API
store
The store module is the core of the TinyBase project and contains the types, interfaces, and functions to work with Store objects.
The main entry point to this module is the createStore function, which returns a new Store. From there, you can set and get data, register listeners, and use other modules to build an entire app around the state and tabular data within.
Interfaces
There is one interface, Store, within the store module.
Store
A Store is the main location for keeping both tabular data and keyed values.
Create a Store easily with the createStore function. From there, you can set and get data, add listeners for when the data changes, set schemas, and so on.
A Store has two facets. It can contain keyed Values, and independently, it can contain tabular Tables data. These two facets have similar APIs but can be used entirely independently: you can use only tables, only keyed Values, or both tables and keyed Values - all in a single Store.
Keyed values
The keyed value support is best thought of as a flat JavaScript object. The Store contains a number of Value objects, each with a unique ID, and which is a string, boolean, or number:
{ // Store
"value1": "one", // Value (string)
"value2": true, // Value (boolean)
"value3": 3, // Value (number)
...
}
In its default form, a Store has no sense of a structured schema for the Values. However, you can optionally specify a ValuesSchema for a Store, which then usefully constrains and defaults the Values you can use.
Tabular data
The tabular data exists in a simple hierarchical structure:
- The
Storecontains a number ofTableobjects. - Each
Tablecontains a number ofRowobjects. - Each
Rowcontains a number ofCellobjects.
A Cell is a string, boolean, or number value.
The members of each level of this hierarchy are identified with a unique Id (which is a string). In other words you can naively think of a Store as a three-level-deep JavaScript object, keyed with strings:
{ // Store
"table1": { // Table
"row1": { // Row
"cell1": "one", // Cell (string)
"cell2": true, // Cell (boolean)
"cell3": 3, // Cell (number)
...
},
...
},
...
}
Again, by default Store has no sense of a structured schema. As long as they are unique within their own parent, the Id keys can each be any string you want. However, as you can optionally specify a TablesSchema for the tabular data in a Store, which then usefully constrains the Table and Cell Ids (and Cell values) you can use.
Setting and getting data
Every part of the Store can be accessed with getter methods. When you retrieve data from the Store, you are receiving a copy - rather than a reference - of it. This means that manipulating the data in the Store must be performed with the equivalent setter and deleter methods.
To benefit from the reactive behavior of the Store, you can also subscribe to changes on any part of it with 'listeners'. Registering a listener returns a listener Id (that you can use later to remove it with the delListener method), and it will then be called every time there is a change within the part of the hierarchy you're listening to.
This table shows the main ways you can set, get, and listen to, different types of data in a Store:
There are two extra methods to manipulate Row objects. The addRow method is like the setRow method but automatically assigns it a new unique Id. And the setPartialRow method lets you update multiple Cell values in a Row without affecting the others. (There is a similar setPartialValues method to do the same for the Values in a Store.)
You can listen to attempts to write invalid data to a Value or Cell with the addInvalidValueListener method or addInvalidCellListener method.
The transaction method is used to wrap multiple changes to the Store so that the relevant listeners only fire once.
The setJson method and the getJson method allow you to work with a JSON-encoded representation of the entire Store, which is useful for persisting it.
Finally, the callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed. This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.
Read more about setting and changing data in The Basics guides, and about listeners in the Listening to Stores guide.
Creating a schema
You can set a ValuesSchema and a TablesSchema with the setValuesSchema method and setTablesSchema method respectively. A TablesSchema constrains the Table Ids the Store can have, and the types of Cell data in each Table. Each Cell requires its type to be specified, and can also take a default value for when it's not specified.
You can also get a serialization of the schemas out of the Store with the getSchemaJson method, and remove the schemas altogether with the delValuesSchema method and delTablesSchema method.
Read more about schemas in the Using Schemas guide.
Convenience methods
There are a few additional helper methods to make it easier to work with a Store. There are methods for easily checking the existence of a Table, Row, or Cell, and iterators that let you act on the children of a common parent:
| Checking existence | Iterator | |
|---|---|---|
Value | hasValue | forEachValue |
Table | hasTable | forEachTable |
Row | hasRow | forEachRow |
Cell | hasCell | forEachCell |
Finally, the getListenerStats method describes the current state of the Store's listeners for debugging purposes.
Example
This example shows a very simple lifecycle of a Store: from creation, to adding and getting some data, and then registering and removing a listener.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getCell('pets', 'fido', 'color'));
// -> 'brown'
const listenerId = store.addTableListener('pets', () => {
console.log('changed');
});
store.setCell('pets', 'fido', 'sold', false);
// -> 'changed'
store.delListener(listenerId);
See also
The Basics guides
Using Schemas guides
Hello World demos
Todo App demos
Getter methods
This is the collection of getter methods within the Store interface. There are 29 getter methods in total.
getTables
The getTables method returns a Tables object containing the entire tabular data of the Store.
getTables(): TablesNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the tabular data in a Store.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example retrieves the Tables of an empty Store, returning an empty object.
const store = createStore();
console.log(store.getTables());
// -> {}
getTablesJson
The getTablesJson method returns a string serialization of all of the Tables in the Store.
getTablesJson(): stringExamples
This example serializes the contents of a Store.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTablesJson());
// -> '{"pets":{"fido":{"species":"dog"}}}'
This example serializes the contents of an empty Store.
const store = createStore();
console.log(store.getTablesJson());
// -> '{}'
Since
v3.0.0
getTablesSchemaJson
The getTablesSchemaJson method returns a string serialization of the TablesSchema of the Store.
getTablesSchemaJson(): string| returns | string | A string serialization of the |
|---|
If no TablesSchema has been set on the Store (or if it has been removed with the delTablesSchema method), then it will return the serialization of an empty object, {}.
Examples
This example serializes the TablesSchema of a Store.
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean'},
},
});
console.log(store.getTablesSchemaJson());
// -> '{"pets":{"species":{"type":"string"},"sold":{"type":"boolean"}}}'
This example serializes the TablesSchema of an empty Store.
const store = createStore();
console.log(store.getTablesSchemaJson());
// -> '{}'
Since
v3.0.0
hasTables
The hasTables method returns a boolean indicating whether any Table objects exist in the Store.
hasTables(): boolean| returns | boolean | Whether any |
|---|
Example
This example shows simple existence checks.
const store = createStore();
console.log(store.hasTables());
// -> false
store.setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTables());
// -> true
hasTablesSchema
The hasTablesSchema method returns a boolean indicating whether the Store currently has a TablesSchema applied to it.
hasTablesSchema(): boolean| returns | boolean | Whether the |
|---|
Example
This example sets a TablesSchema and checks that it is present.
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
console.log(store.hasTablesSchema());
// -> true
store.delTablesSchema();
console.log(store.hasTablesSchema());
// -> false
Since
v4.1.1
getTableIds
The getTableIds method returns the Ids of every Table in the Store.
getTableIds(): IdsNote that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Table Ids in a Store.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTableIds());
// -> ['pets', 'species']
This example retrieves the Table Ids of an empty Store, returning an empty array.
const store = createStore();
console.log(store.getTableIds());
// -> []
getTable
The getTable method returns an object containing the entire data of a single Table in the Store.
getTable(tableId: string): TableNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTable('pets'));
// -> {fido: {species: 'dog'}}
This example retrieves a Table that does not exist, returning an empty object.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTable('employees'));
// -> {}
getTableCellIds
The getTableCellIds method returns the Ids of every Cell used across the whole Table.
getTableCellIds(tableId: string): Ids| Type | Description | |
|---|---|---|
tableId | string | |
| returns | Ids | An array of the |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Cell Ids used across a whole Table.
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', legs: 4},
cujo: {dangerous: true},
},
});
console.log(store.getTableCellIds('pets'));
// -> ['species', 'color', 'legs', 'dangerous']
This example retrieves the Cell Ids used across a Table that does not exist, returning an empty array.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTableCellIds('species'));
// -> []
Since
v3.3.0
hasTable
The hasTable method returns a boolean indicating whether a given Table exists in the Store.
hasTable(tableId: string): boolean| Type | Description | |
|---|---|---|
tableId | string | |
| returns | boolean |
Example
This example shows two simple Table existence checks.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTable('pets'));
// -> true
console.log(store.hasTable('employees'));
// -> false
hasTableCell
The hasTableCell method returns a boolean indicating whether a given Cell exists anywhere in a Table, not just in a specific Row.
hasTableCell(
tableId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
cellId | string | |
| returns | boolean |
Example
This example shows two simple Cell existence checks.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {legs: 4}},
});
console.log(store.hasTableCell('pets', 'species'));
// -> true
console.log(store.hasTableCell('pets', 'legs'));
// -> true
console.log(store.hasTableCell('pets', 'color'));
// -> false
Since
v3.3.0
getRowIds
The getRowIds method returns the Ids of every Row in a given Table.
getRowIds(tableId: string): Ids| Type | Description | |
|---|---|---|
tableId | string | |
| returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Row Ids in a Table.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowIds('pets'));
// -> ['fido', 'felix']
This example retrieves the Row Ids of a Table that does not exist, returning an empty array.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowIds('employees'));
// -> []
getSortedRowIds
The getSortedRowIds method returns the Ids of every Row in a given Table, sorted according to the values in a specified Cell.
getSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
| returns | Ids |
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the Table is large. For a performant approach to tracking the sorted Row Ids when they change, use the addSortedRowIdsListener method.
If the Table does not exist, an empty array is returned.
Examples
This example retrieves sorted Row Ids in a Table.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species'));
// -> ['felix', 'fido']
This example retrieves sorted Row Ids in a Table in reverse order.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets', 'species', true));
// -> ['cujo', 'fido', 'felix']
This example retrieves two pages of Row Ids in a Table.
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
console.log(store.getSortedRowIds('pets', 'price', false, 0, 2));
// -> ['lowly', 'mickey']
console.log(store.getSortedRowIds('pets', 'price', false, 2, 2));
// -> ['carnaby', 'tom']
This example retrieves Row Ids sorted by their own value, since the cellId parameter is undefined.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets'));
// -> ['cujo', 'felix', 'fido']
This example retrieves the sorted Row Ids of a Table that does not exist, returning an empty array.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getSortedRowIds('employees'));
// -> []
Since
v2.0.0
getRow
The getRow method returns an object containing the entire data of a single Row in a given Table.
getRow(
tableId: string,
rowId: string,
): RowNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the data in a single Row.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}
This example retrieves a Row that does not exist, returning an empty object.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
getRowCount
The getRowCount method returns the count of the Row objects in a given Table.
getRowCount(tableId: string): number| Type | Description | |
|---|---|---|
tableId | string | |
| returns | number |
While this provides the same result as the length of Ids array returned from the getRowIds method, it is somewhat faster, and useful for efficient pagination.
Examples
This example retrieves the number of Row objects in the Table.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getRowCount('pets'));
// -> 2
This example retrieves the Row Ids of a Table that does not exist, returning zero.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowCount('employees'));
// -> 0
Since
v4.1.0
hasRow
The hasRow method returns a boolean indicating whether a given Row exists in the Store.
hasRow(
tableId: string,
rowId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | boolean |
Example
This example shows two simple Row existence checks.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasRow('pets', 'fido'));
// -> true
console.log(store.hasRow('pets', 'felix'));
// -> false
getCellIds
The getCellIds method returns the Ids of every Cell in a given Row in a given Table.
getCellIds(
tableId: string,
rowId: string,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
| returns | Ids |
Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Cell Ids in a Row.
const store = createStore().setTables({
pets: {
fido: {species: 'dog', color: 'brown'},
},
});
console.log(store.getCellIds('pets', 'fido'));
// -> ['species', 'color']
This example retrieves the Cell Ids of a Row that does not exist, returning an empty array.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCellIds('pets', 'felix'));
// -> []
getCell
The getCell method returns the value of a single Cell in a given Row, in a given Table.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | CellOrUndefined | The value of the |
Examples
This example retrieves a single Cell.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'
This example retrieves a Cell that does not exist, returning undefined.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
hasCell
The hasCell method returns a boolean indicating whether a given Cell exists in a given Row in a given Table.
hasCell(
tableId: string,
rowId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
| returns | boolean | Whether a |
Example
This example shows two simple Cell existence checks.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasCell('pets', 'fido', 'species'));
// -> true
console.log(store.hasCell('pets', 'fido', 'color'));
// -> false
getValues
The getValues method returns an object containing the entire set of keyed Values in the Store.
getValues(): ValuesNote that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.
Examples
This example retrieves the set of keyed Values in the Store.
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example retrieves Values from a Store that has none, returning an empty object.
const store = createStore();
console.log(store.getValues());
// -> {}
Since
v3.0.0
getValuesJson
The getValuesJson method returns a string serialization of all of the keyed Values in the Store.
getValuesJson(): stringExamples
This example serializes the keyed value contents of a Store.
const store = createStore().setValues({open: true});
console.log(store.getValuesJson());
// -> '{"open":true}'
This example serializes the contents of an empty Store.
const store = createStore();
console.log(store.getValuesJson());
// -> '{}'
Since
v3.0.0
getValuesSchemaJson
The getValuesSchemaJson method returns a string serialization of the ValuesSchema of the Store.
getValuesSchemaJson(): string| returns | string | A string serialization of the |
|---|
If no ValuesSchema has been set on the Store (or if it has been removed with the delValuesSchema method), then it will return the serialization of an empty object, {}.
Examples
This example serializes the ValuesSchema of a Store.
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
console.log(store.getValuesSchemaJson());
// -> '{"open":{"type":"boolean","default":false}}'
This example serializes the ValuesSchema of an empty Store.
const store = createStore();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
hasValues
The hasTables method returns a boolean indicating whether any Values exist in the Store.
hasValues(): boolean| returns | boolean | Whether any |
|---|
Example
This example shows simple existence checks.
const store = createStore();
console.log(store.hasValues());
// -> false
store.setValues({open: true});
console.log(store.hasValues());
// -> true
Since
v3.0.0
hasValuesSchema
The hasValuesSchema method returns a boolean indicating whether the Store currently has a ValuesSchema applied to it.
hasValuesSchema(): boolean| returns | boolean | Whether the |
|---|
Example
This example sets a ValuesSchema and checks that it is present.
const store = createStore().setValuesSchema({open: {type: 'boolean'}});
console.log(store.hasValuesSchema());
// -> true
store.delValuesSchema();
console.log(store.hasValuesSchema());
// -> false
Since
v4.1.1
getValue
The getValue method returns a single keyed Value in the Store.
getValue(valueId: string): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
| returns | ValueOrUndefined | The |
Examples
This example retrieves a single Value.
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('employees'));
// -> 3
This example retrieves a Value that does not exist, returning undefined.
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValue('website'));
// -> undefined
Since
v3.0.0
getValueIds
The getValueIds method returns the Ids of every Value in a Store.
getValueIds(): IdsNote that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself.
Examples
This example retrieves the Value Ids in a Store.
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValueIds());
// -> ['open', 'employees']
This example retrieves the Value Ids of a Store that has had none set, returning an empty array.
const store = createStore();
console.log(store.getValueIds());
// -> []
Since
v3.0.0
hasValue
The hasValue method returns a boolean indicating whether a given Value exists in the Store.
hasValue(valueId: string): boolean| Type | Description | |
|---|---|---|
valueId | string | |
| returns | boolean |
Example
This example shows two simple Value existence checks.
const store = createStore().setValues({open: true});
console.log(store.hasValue('open'));
// -> true
console.log(store.hasValue('employees'));
// -> false
Since
v3.0.0
getContent
The getContent method returns a Tables object and a Values object in an array, representing the entire content of the Store.
getContent(): [Tables, Values]Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned objects are not made to the Store itself.
Examples
This example retrieves the content of a Store.
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true, employees: 3});
console.log(store.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true, employees: 3}]
This example retrieves the Tables and Values of an empty Store, returning empty objects.
const store = createStore();
console.log(store.getContent());
// -> [{}, {}]
Since
v4.0.0
getJson
The getJson method returns a string serialization of all the Store content: both the Tables and the keyed Values.
getJson(): stringFrom v3.0 onwards, the serialization is of an array with two entries. The first is the Tables object, the second the Values. In previous versions (before the existence of the Values data structure), it was a sole object of Tables.
Examples
This example serializes the tabular and keyed value contents of a Store.
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setValues({open: true});
console.log(store.getJson());
// -> '[{"pets":{"fido":{"species":"dog"}}},{"open":true}]'
This example serializes the contents of an empty Store.
const store = createStore();
console.log(store.getJson());
// -> '[{},{}]'
getSchemaJson
The getSchemaJson method returns a string serialization of both the TablesSchema and ValuesSchema of the Store.
getSchemaJson(): string| returns | string | A string serialization of the |
|---|
From v3.0 onwards, the serialization is of an array with two entries. The first is the TablesSchema object, the second the ValuesSchema. In previous versions (before the existence of the ValuesSchema data structure), it was a sole object of TablesSchema.
Examples
This example serializes the TablesSchema and ValuesSchema of a Store.
const store = createStore()
.setTablesSchema({
pets: {
price: {type: 'number'},
},
})
.setValuesSchema({
open: {type: 'boolean'},
});
console.log(store.getSchemaJson());
// -> '[{"pets":{"price":{"type":"number"}}},{"open":{"type":"boolean"}}]'
This example serializes the TablesSchema and ValuesSchema of an empty Store.
const store = createStore();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Setter methods
This is the collection of setter methods within the Store interface. There are 17 setter methods in total.
setTables
The setTables method takes an object and sets the entire tabular data of the Store.
setTables(tables: Tables): StoreThis method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Tables type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Tables object is valid, any data that was already present in the Store will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the tabular data of a Store.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}
This example attempts to set the tabular data of an existing Store with partly invalid, and then completely invalid, Tables objects.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTables({pets: {felix: {species: 'cat', bug: []}}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTables({meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
setTablesJson
The setTablesJson method takes a string serialization of all of the Tables in the Store and attempts to update them to that.
setTablesJson(tablesJson: string): StoreIf the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any TablesSchema associated with the Store).
Examples
This example sets the tabular contents of a Store from a serialization.
const store = createStore();
store.setTablesJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the tabular contents of a Store from an invalid serialization.
const store = createStore();
store.setTablesJson('{"pets": {"fido": {');
console.log(store.getTables());
// -> {}
Since
v3.0.0
setTablesSchema
The setTablesSchema method lets you specify the TablesSchema of the tabular part of the Store.
setTablesSchema(tablesSchema: TablesSchema): Store| Type | Description | |
|---|---|---|
tablesSchema | TablesSchema | The |
| returns | Store | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, or Cell objects are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing TablesSchema with the delTablesSchema method.
Example
This example sets the TablesSchema of a Store after it has been created.
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Since
v3.0.0
setTable
The setTable method takes an object and sets the entire data of a single Table in the Store.
setTable(
tableId: string,
table: Table,
): StoreThis method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Table type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Table object is valid, any data that was already present in the Store for that Table will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Table.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Table objects.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setTable('pets', {felix: {species: 'cat', bug: []}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
store.setTable('pets', {meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
setRow
The setRow method takes an object and sets the entire data of a single Row in the Store.
setRow(
tableId: string,
rowId: string,
row: Row,
): Store| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
| returns | Store | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, any data that was already present in the Store for that Row will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a single Row.
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Row objects.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
addRow
The addRow method takes an object and creates a new Row in the Store, returning the unique Id assigned to it.
addRow(
tableId: string,
row: Row,
reuseRowIds?: boolean,
): undefined | string| Type | Description | |
|---|---|---|
tableId | string | |
row | Row | The data of a single |
reuseRowIds? | boolean | Whether |
| returns | undefined | string | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, a new Row will be created. If the object is completely invalid, no change will be made to the Store and the method will return undefined
You should not guarantee the form of the unique Id that is generated when a Row is added to the Table. However it is likely to be a string representation of an increasing integer.
The reuseRowIds parameter defaults to true, which means that if you delete a Row and then add another, the Id will be re-used - unless you delete the entire Table, in which case all Row Ids will reset. Otherwise, if you specify reuseRowIds to be false, then the Id will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Examples
This example adds a single Row.
const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}
This example attempts to add Rows to an existing Store with partly invalid, and then completely invalid, Row objects.
const store = createStore().setTables({pets: {'0': {species: 'dog'}}});
console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
setPartialRow
The setPartialRow method takes an object and sets partial data of a single Row in the Store, leaving other Cell values unaffected.
setPartialRow(
tableId: string,
rowId: string,
partialRow: Row,
): Store| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
partialRow | Row | The partial data of a single |
| returns | Store | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Row type, or because, when combined with the current Row data, it does not match a TablesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Row object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the data of a single Row.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.setPartialRow('pets', 'fido', {color: 'walnut', visits: 1});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'walnut', visits: 1}}}
This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Row objects.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setPartialRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
store.setPartialRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
setCell
The setCell method sets the value of a single Cell in the Store.
setCell(
tableId: string,
rowId: string,
cellId: string,
cell: Cell | MapCell,
): Store| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
| returns | Store | A reference to the |
This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.
If the Cell value is invalid (either because of its type, or because it does not match a TablesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Cell types, this method can also take a MapCell function that takes the current Cell value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the value of a single Cell.
const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example sets the data of a single Cell by mapping the existing value.
const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});
store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2
This example attempts to set the data of an existing Store with an invalid Cell value.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
setPartialValues
The setPartialValues method takes an object and sets its Values in the Store, but leaving existing Values unaffected.
setPartialValues(partialValues: Values): StoreThis method will cause listeners to be called for any Values or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Values type, or because, when combined with the current Values data, it does not match a ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Values object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets some of the keyed value data in a Store.
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Values objects.
const store = createStore().setValues({open: true});
store.setPartialValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setPartialValues(42);
console.log(store.getValues());
// -> {open: true, employees: 3}
Since
v3.0.0
setValues
The setValues method takes an object and sets all the Values in the Store.
setValues(values: Values): StoreThis method will cause listeners to be called for any Value or Id changes resulting from it.
Any part of the provided object that is invalid (either according to the Values type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Values object is valid, any data that was already present in the Store for that Values will be completely overwritten. If the object is completely invalid, no change will be made to the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the Values of a Store.
const store = createStore().setValues({open: true, employees: 3});
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Values objects.
const store = createStore().setValues({open: true});
store.setValues({employees: 3, bug: []});
console.log(store.getValues());
// -> {employees: 3}
store.setValues(42);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
setValuesJson
The setValuesJson method takes a string serialization of all of the Values in the Store and attempts to update them to those values.
setValuesJson(valuesJson: string): StoreIf the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setValues method (according to the Values type, and matching any ValuesSchema associated with the Store).
Examples
This example sets the keyed value contents of a Store from a serialization.
const store = createStore();
store.setValuesJson('{"open": true}');
console.log(store.getValues());
// -> {open: true}
This example attempts to set the keyed value contents of a Store from an invalid serialization.
const store = createStore();
store.setValuesJson('{"open": false');
console.log(store.getValues());
// -> {}
Since
v3.0.0
setValuesSchema
The setValuesSchema method lets you specify the ValuesSchema of the keyed Values part of the Store.
setValuesSchema(valuesSchema: ValuesSchema): Store| Type | Description | |
|---|---|---|
valuesSchema | ValuesSchema | The |
| returns | Store | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Values are removed. These changes will fire any listeners to that data, as expected.
When no longer needed, you can also completely remove an existing ValuesSchema with the delValuesSchema method.
Example
This example sets the ValuesSchema of a Store after it has been created.
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
});
store.setValue('open', 'maybe');
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
setValue
The setValue method sets a single keyed Value in the Store.
setValue(
valueId: string,
value: Value | MapValue,
): StoreThis method will cause listeners to be called for any Value, or Id changes resulting from it.
If the Value is invalid (either because of its type, or because it does not match a ValuesSchema associated with the Store), will be ignored silently.
As well as string, number, or boolean Value types, this method can also take a MapValue function that takes the current Value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets a single Value.
const store = createStore().setValue('open', true);
console.log(store.getValues());
// -> {open: true}
This example sets the data of a single Value by mapping the existing Value.
const increment = (value) => value + 1;
const store = createStore().setValues({employees: 3});
store.setValue('employees', increment);
console.log(store.getValue('employees'));
// -> 4
This example attempts to set an invalid Value.
const store = createStore().setValues({employees: 3});
store.setValue('bug', []);
console.log(store.getValues());
// -> {employees: 3}
Since
v3.0.0
setContent
The setContent method takes an array of two objects and sets the entire data of the Store.
setContent(content: [Tables, Values]): Store| Type | Description | |
|---|---|---|
content | [Tables, Values] | An array containing the tabular and keyed-value data of the |
| returns | Store |
This method will cause listeners to be called for any Table, Row, Cell, Value, or Id changes resulting from it.
Any part of the provided objects that are invalid (either according to the Tables or Values type, or because it does not match a TablesSchema or ValuesSchema associated with the Store), will be ignored silently.
Assuming that at least some of the provided Tables object or Values object is valid, any data that was already present in that part of the Store will be completely overwritten. If either object is completely invalid, no change will be made to the corresponding part of the Store.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Examples
This example sets the data of a Store.
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
This example attempts to set the data of an existing Store with partly invalid, and then completely invalid objects.
const store = createStore().setContent([
{pets: {fido: {species: 'dog'}}},
{open: true, employees: 3},
]);
store.setContent([{pets: {felix: {species: 'cat', bug: []}}}, '']);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
console.log(store.getValues());
// -> {open: true, employees: 3}
store.setContent([{meaning: 42}]);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
Since
v4.0.0
setJson
The setJson method takes a string serialization of all of the Tables and Values in the Store and attempts to update them to those values.
setJson(tablesAndValuesJson: string): Store| Type | Description | |
|---|---|---|
tablesAndValuesJson | string | A string serialization of all of the |
| returns | Store | A reference to the |
From v3.0 onwards, the serialization should be of an array with two entries. The first is the Tables object, the second the Values. In previous versions (before the existence of the Values data structure), it was a sole object of Tables. For backwards compatibility, if a serialization of a single object is provided, it will be treated as the Tables type.
If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any TablesSchema associated with the Store), and the setValues method (according to the Values type, and matching any ValuesSchema associated with the Store).
Examples
This example sets the tabular and keyed value contents of a Store from a serialization.
const store = createStore();
store.setJson('[{"pets": {"fido": {"species": "dog"}}}, {"open": true}]');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {open: true}
This example sets the tabular contents of a Store from a legacy single-object serialization (compatible with v2.x and earlier).
const store = createStore();
store.setJson('{"pets": {"fido": {"species": "dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
console.log(store.getValues());
// -> {}
This example attempts to set both the tabular and keyed value contents of a Store from an invalid serialization.
const store = createStore();
store.setValuesJson('[{"pets": {"fido": {"species": "do');
console.log(store.getTables());
// -> {}
console.log(store.getValues());
// -> {}
setSchema
The setSchema method lets you specify the TablesSchema and ValuesSchema of the Store.
setSchema(
tablesSchema: TablesSchema,
valuesSchema?: ValuesSchema,
): Store| Type | Description | |
|---|---|---|
tablesSchema | TablesSchema | The |
valuesSchema? | ValuesSchema | The |
| returns | Store | A reference to the |
Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, Cell, or Value objects are removed. These changes will fire any listeners to that data, as expected.
From v3.0 onwards, this method takes two arguments. The first is the TablesSchema object, the second the ValuesSchema. In previous versions (before the existence of the ValuesSchema data structure), only the first was present. For backwards compatibility the new second parameter is optional.
Examples
This example sets the TablesSchema and ValuesSchema of a Store after it has been created.
const store = createStore().setSchema(
{
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
},
{open: {type: 'boolean', default: false}},
);
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
store.setValue('open', 'maybe');
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
console.log(store.getValues());
// -> {open: false}
This example sets just the TablesSchema of a Store after it has been created.
const store = createStore().setSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
setTransactionChanges
The setTransactionChanges method applies a set of TransactionChanges to the Store.
setTransactionChanges(transactionChanges: TransactionChanges): Store| Type | Description | |
|---|---|---|
transactionChanges | TransactionChanges | The |
| returns | Store | A reference to the |
This method will take a TransactionChanges object (which is available at the end of a transaction) and apply it to a Store. The most likely need to do this is to take the changes made during the transaction of one Store, and apply it to the content of another Store - such as when persisting and synchronizing data.
Any part of the provided TransactionChanges object are invalid (either because of its type, or because it does not match the schemas associated with the Store) will be ignored silently.
The method returns a reference to the Store so that subsequent operations can be chained in a fluent style.
Example
This example applies a TransactionChanges object that sets a Cell and removes a Value.
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.setTransactionChanges([
{pets: {fido: {color: 'black'}}},
{open: null},
]);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(store.getValues());
// -> {}
Since
v4.0.0
Listener methods
This is the collection of listener methods within the Store interface. There are 20 listener methods in total.
addTablesListener
The addTablesListener method registers a listener function with the Store that will be called whenever data in the Store changes.
addTablesListener(
listener: TablesListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | TablesListener | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TablesListener function, and will be called with a reference to the Store and a GetCellChange function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the whole Store.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener((store, getCellChange) => {
console.log('Tables changed');
console.log(getCellChange('pets', 'fido', 'color'));
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to the whole Store, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(
(store) => store.setCell('meta', 'update', 'store', true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
addTableIdsListener
The addTableIdsListener method registers a listener function with the Store that will be called whenever the Table Ids in the Store change.
addTableIdsListener(
listener: TableIdsListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | TableIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableIdsListener function, and will be called with a reference to the Store.
By default, such a listener is only called when a Table is added or removed. To listen to all changes in the Store, use the addTablesListener method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Table Ids.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener((store) => {
console.log('Table Ids changed');
console.log(store.getTableIds());
});
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
// -> ['pets', 'species']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Table Ids, and which also mutates the Store itself.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener(
(store) => store.setCell('meta', 'update', 'store', true),
true, // mutator
);
store.setTable('species', {dog: {price: 5}});
console.log(store.getTable('meta'));
// -> {update: {store: true}}
store.delListener(listenerId);
addTableCellIdsListener
The addTableCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids that appear anywhere in a Table change.
addTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableCellIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.
By default, such a listener is only called when a Cell Id is added or removed from the whole of the Table. To listen to all changes in the Table, use the addTableListener method.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell Ids that appear anywhere in a Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener('pets', (store) => {
console.log('Cell Ids in pets table changed');
console.log(store.getTableCellIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids that appear anywhere in any Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
species: {dog: {price: 5}},
});
const listenerId = store.addTableCellIdsListener(
null,
(store, tableId) => {
console.log(`Cell Ids in ${tableId} table changed`);
console.log(store.getTableCellIds(tableId));
},
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
// -> 'Cell Ids in pets table changed'
// -> ['species', 'color', 'legs']
store.setRow('species', 'cat', {price: 4, friendly: true});
// -> 'Cell Ids in species table changed'
// -> ['price', 'friendly']
store.delListener(listenerId);
This example registers a listener that responds to the Cell Ids that appear anywhere in a Table, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableCellIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat', legs: 4});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
addTableListener
The addTableListener method registers a listener function with the Store that will be called whenever data in a Table changes.
addTableListener(
tableId: IdOrNull,
listener: TableListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a TableListener function, and will be called with a reference to the Store, the Id of the Table that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId, getCellChange) => {
console.log('pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(null, (store, tableId) => {
console.log(`${tableId} table changed`);
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Table, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
addRowIdsListener
The addRowIdsListener method registers a listener function with the Store that will be called whenever the Row Ids in a Table change.
addRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.
By default, such a listener is only called when a Row is added or removed. To listen to all changes in the Table, use the addTableListener method.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Row Ids of a specific Table.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener('pets', (store) => {
console.log('Row Ids for pets table changed');
console.log(store.getRowIds('pets'));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row Ids of any Table.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids for ${tableId} table changed`);
console.log(store.getRowIds(tableId));
});
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.setRow('species', 'dog', {price: 5});
// -> 'Row Ids for species table changed'
// -> ['dog']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row Ids of a specific Table, and which also mutates the Store itself.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(
'pets',
(store, tableId) => store.setCell('meta', 'update', tableId, true),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
addSortedRowIdsListener
The addSortedRowIdsListener method registers a listener function with the Store that will be called whenever sorted (and optionally, paginated) Row Ids in a Table change.
addSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a SortedRowIdsListener function, and will be called with a reference to the Store, the Id of the Table whose Row Ids sorting changed, the Cell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Such a listener is called when a Row is added or removed, but also when a value in the specified Cell (somewhere in the Table) has changed enough to change the sorting of the Row Ids.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified Table, sorted by a single specified Cell.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table.
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'cujo']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'fido', {species: 'dog'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['felix', 'fido', 'cujo']
store.delListener(listenerId);
This example registers a listener that responds to any change to a paginated section of the sorted Row Ids of a specific Table.
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'price',
false,
0,
3,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First three sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds('pets', 'price', false, 0, 3));
// -> ['lowly', 'mickey', 'carnaby']
store.setCell('pets', 'carnaby', 'price', 4.5);
// -> 'First three sorted Row Ids for pets table changed'
// -> ['lowly', 'mickey', 'tom']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table. The Row Ids are sorted by their own value, since the cellId parameter is explicitly undefined.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', undefined, false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
undefined,
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['cujo', 'felix', 'fido']
store.delListener(listenerId);
This example registers a listener that responds to a change in the sorting of the rows of a specific Table, even though the set of Ids themselves has not changed.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setCell('pets', 'felix', 'species', 'tiger');
// -> 'Sorted Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row Ids of a specific Table, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId) => store.setCell('meta', 'sorted', tableId, true),
true, // mutator
);
store.setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTable('meta'));
// -> {sorted: {pets: true}}
store.delListener(listenerId);
Since
v2.0.0
addRowCountListener
The addRowCountListener method registers a listener function with the Store that will be called whenever the count of Row objects in a Table change.
addRowCountListener(
tableId: IdOrNull,
listener: RowCountListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowCountListener | The function that will be called whenever the number of |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowCountListener function, and will be called with a reference to the Store, the Id of the Table that changed, and the number of Row objects in the Table.
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to a change in the number of Row objects in a specific Table.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, _tableId, count) => {
console.log('Row count for pets table changed to ' + count);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row objects of any Table.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
null,
(store, tableId, count) => {
console.log(`Row count for ${tableId} table changed to ${count}`);
},
);
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row count for pets table changed to 2'
store.setRow('species', 'dog', {price: 5});
// -> 'Row count for species table changed to 1'
store.delListener(listenerId);
This example registers a listener that responds to any change to a change in the number of Row objects of a specific Table, and which also mutates the Store itself.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowCountListener(
'pets',
(store, tableId, count) =>
store.setCell('meta', 'update', tableId, count),
true, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: 2}}
store.delListener(listenerId);
Since
v4.1.0
addRowListener
The addRowListener method registers a listener function with the Store that will be called whenever data in a Row changes.
addRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a RowListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Row.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId, getCellChange) => {
console.log('fido row in pets table changed');
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Row.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
null,
null,
(store, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Row, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
addCellIdsListener
The addCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids in a Row change.
addCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellIdsListener function, and will be called with a reference to the Store, the Id of the Table, and the Id of the Row that changed.
By default, such a listener is only called when a Cell is added or removed. To listen to all changes in the Row, use the addRowListener method.
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing a null wildcard).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Cell Ids of a specific Row.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener('pets', 'fido', (store) => {
console.log('Cell Ids for fido row in pets table changed');
console.log(store.getCellIds('pets', 'fido'));
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids of any Row.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
null,
null,
(store, tableId, rowId) => {
console.log(`Cell Ids for ${rowId} row in ${tableId} table changed`);
console.log(store.getCellIds(tableId, rowId));
},
);
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.setCell('species', 'dog', 'price', 5);
// -> 'Cell Ids for dog row in species table changed'
// -> ['price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell Ids of a specific Row, and which also mutates the Store itself.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
'pets',
'fido',
(store, tableId, rowId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
true, // mutator
);
store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}
store.delListener(listenerId);
addCellListener
The addCellListener method registers a listener function with the Store that will be called whenever data in a Cell changes.
addCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a CellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, the Id of the Cell that changed, the new Cell value, the old Cell value, and a GetCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
addInvalidCellListener
The addInvalidCellListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Cell.
addInvalidCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: InvalidCellListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | InvalidCellListener | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is an InvalidCellListener function, and will be called with a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that was being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or invalid attempts to change any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a TablesSchema is present. The listener will be called:
- if a
Tableis being updated that is not specified in theTablesSchema - if a
Cellis of the wrong type specified in theTablesSchema - if a
Cellis omitted and is not defaulted in theTablesSchema - if an empty
Rowis provided and there are noCelldefaults in theTablesSchema
The listener will not be called if a Cell that is defaulted in the TablesSchema is not provided, as long as all of the Cells that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the TablesSchema example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Cell.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) => {
console.log('Invalid color cell in fido row in pets table');
console.log(invalidCells);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell - in a Store without a TablesSchema. Note also how it then responds to cases where empty or invalid Row objects, or Table objects, or Tables objects are provided.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'
store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'
store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'
store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'
store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'
store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell - in a Store with a TablesSchema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell values in a Row.
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string', default: 'unknown'},
},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema
store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set
store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted
store.delTables().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
});
store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) =>
store.setCell(
'meta',
'invalid_updates',
`${tableId}_${rowId}_${cellId}`,
JSON.stringify(invalidCells[0]),
),
true,
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
store.delListener(listenerId);
Since
v1.1.0
addValuesListener
The addValuesListener method registers a listener function with the Store that will be called whenever the Values change.
addValuesListener(
listener: ValuesListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | ValuesListener | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValuesListener function, and will be called with a reference to the Store and a GetValueChange function in case you need to inspect any changes that occurred.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to the Store's Values.
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener((store, getValueChange) => {
console.log('values changed');
console.log(getValueChange('employees'));
});
store.setValue('employees', 4);
// -> 'values changed'
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to the Store's Values, and which also mutates the Store itself.
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValuesListener(
(store, getValueChange) => store.setValue('updated', true),
true,
);
store.setValue('employees', 4);
console.log(store.getValues());
// -> {open: true, employees: 4, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addInvalidValueListener
The addInvalidValueListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Value.
addInvalidValueListener(
valueId: IdOrNull,
listener: InvalidValueListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | InvalidValueListener | The function that will be called whenever an attempt to write invalid data to the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is an InvalidValueListener function, and will be called with a reference to the Store and the Id of Value that was being attempted to be changed. It is also given the invalid value of the Value, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Value (by specifying the Value Id as the method's first parameter) or invalid attempts to change any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a ValuesSchema is present. The listener will be called:
- if a
Valueis being updated that is not specified in theValuesSchema - if a
Valueis of the wrong type specified in theValuesSchema - if a
Valueis omitted when using setValues that is not defaulted in theValuesSchema
The listener will not be called if a Value that is defaulted in the ValuesSchema is not provided, as long as all of the Values that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the ValuesSchema example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Value.
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) => {
console.log('Invalid open value');
console.log(invalidValues);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
// -> [{yes: true}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value - in a Store without a ValuesSchema. Note also how it then responds to cases where an empty Values object is provided.
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('open', {yes: true});
// -> 'Invalid open value'
store.setValue('employees', ['alice', 'bob']);
// -> 'Invalid employees value'
store.setValues('pets', 'felix', {});
// -> 'Invalid undefined value'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Value - in a Store with a ValuesSchema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Values.
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
console.log(store.getValues());
// -> {open: false}
const listenerId = store.addInvalidValueListener(
null,
(store, valueId) => {
console.log(`Invalid ${valueId} value`);
},
);
store.setValue('website', true);
// -> 'Invalid website value'
// The listener is called, because the website Value is not in the schema
store.setValue('open', 'yes');
// -> 'Invalid open value'
// The listener is called, because 'open' is invalid...
console.log(store.getValues());
// -> {open: false}
// ...even though it is still present with the default value
store.setValues({open: true});
// -> 'Invalid employees value'
// The listener is called because employees is missing and not defaulted...
console.log(store.getValues());
// -> {open: true}
// ...even though the Values were set
store.setValues({employees: 3});
console.log(store.getValues());
// -> {open: false, employees: 3}
// The listener is not called, because 'open' is defaulted
store.setValuesSchema({
open: {type: 'boolean'},
employees: {type: 'number'},
});
store.setValues({});
// -> 'Invalid open value'
// -> 'Invalid employees value'
// -> 'Invalid undefined value'
// The listener is called multiple times, because neither Value is
// defaulted and the Values as a whole were empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
const store = createStore().setValues({open: true});
const listenerId = store.addInvalidValueListener(
'open',
(store, valueId, invalidValues) =>
store.setValue('invalid_updates', JSON.stringify(invalidValues[0])),
true,
);
store.setValue('open', {yes: true});
console.log(store.getValue('invalid_updates'));
// -> '{"yes":true}'
store.delListener(listenerId);
Since
v3.0.0
addValueIdsListener
The addValueIdsListener method registers a listener function with the Store that will be called whenever the Value Ids in a Store change.
addValueIdsListener(
listener: ValueIdsListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
listener | ValueIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueIdsListener function, and will be called with a reference to the Store.
By default, such a listener is only called when a Value is added or removed. To listen to all changes in the Values, use the addValuesListener method.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any change to the Value Ids.
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener((store) => {
console.log('Value Ids changed');
console.log(store.getValueIds());
});
store.setValue('employees', 3);
// -> 'Value Ids changed'
// -> ['open', 'employees']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Value Ids, and which also mutates the Store itself.
const store = createStore().setValues({open: true});
const listenerId = store.addValueIdsListener(
(store) => store.setValue('updated', true),
true, // mutator
);
store.setValue('employees', 3);
console.log(store.getValues());
// -> {open: true, employees: 3, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addValueListener
The addValueListener method registers a listener function with the Store that will be called whenever data in a Value changes.
addValueListener(
valueId: IdOrNull,
listener: ValueListener,
mutator?: boolean,
): string| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | ValueListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
| returns | string | A unique |
The provided listener is a ValueListener function, and will be called with a reference to the Store, the Id of the Value that changed, the new Value value, the old Value, and a GetValueChange function in case you need to inspect any changes that occurred.
You can either listen to a single Value (by specifying the Value Id) or changes to any Value (by providing a null wildcard).
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Value.
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId, newValue, oldValue, getValueChange) => {
console.log('employee value changed');
console.log([oldValue, newValue]);
console.log(getValueChange('employees'));
},
);
store.setValue('employees', 4);
// -> 'employee value changed'
// -> [3, 4]
// -> [true, 3, 4]
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Value.
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(null, (store, valueId) => {
console.log(`${valueId} value changed`);
});
store.setValue('employees', 4);
// -> 'employees value changed'
store.setValue('open', false);
// -> 'open value changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Value, and which also mutates the Store itself.
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
'employees',
(store, valueId) => store.setValue('updated', true),
true,
);
store.delValue('employees');
console.log(store.getValues());
// -> {open: true, updated: true}
store.delListener(listenerId);
Since
v3.0.0
addDidFinishTransactionListener
The addDidFinishTransactionListener method registers a listener function with the Store that will be called just after other non-mutating listeners are called at the end of the transaction.
addDidFinishTransactionListener(listener: TransactionListener): string| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called after the end of a transaction. |
| returns | string | A unique |
This is useful if you need to know that a set of listeners have just been called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. The two flags is intended as a hint about whether non-mutating listeners might have been called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
Note that a TransactionListener added to the Store with this method cannot mutate the Store itself, and attempts to do so will fail silently.
Example
This example registers a listener that is called at the end of the transaction, just after its listeners have been called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched and valuesTouched parameters in the listener work.
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addDidFinishTransactionListener(
(store, _, getTransactionLog) => {
const {cellsTouched, valuesTouched} = getTransactionLog?.() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
},
);
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Tables changed'
// -> 'Cells/Values touched: true/false'
store.transaction(() => store.setValue('employees', 4));
// -> 'Values changed'
// -> 'Cells/Values touched: false/true'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: undefined/undefined'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
addStartTransactionListener
The addStartTransactionListener method registers a listener function with the Store that will be called at the start of a transaction.
addStartTransactionListener(listener: TransactionListener): string| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called at the start of a transaction. |
| returns | string | A unique |
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. Since this is called at the start, they will both be false!
Note that a TransactionListener added to the Store with this method can mutate the Store, and its changes will be treated as part of the transaction that is starting.
Example
This example registers a listener that is called at start end of the transaction, just before its listeners will be called.
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addStartTransactionListener(
(store, cellsTouched, valuesTouched) => {
console.log('Transaction started');
},
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Transaction started'
store.callListener(listenerId);
// -> 'Transaction started'
store.delListener(listenerId);
Since
v3.2.0
addWillFinishTransactionListener
The addWillFinishTransactionListener method registers a listener function with the Store that will be called just before other non-mutating listeners are called at the end of the transaction.
addWillFinishTransactionListener(listener: TransactionListener): string| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called before the end of a transaction. |
| returns | string | A unique |
This is useful if you need to know that a set of listeners are about to be called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener will receive a reference to the Store and two booleans to indicate whether Cell or Value data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
Note that a TransactionListener added to the Store with this method can mutate the Store itself, and its changes will be treated as part of the transaction that is starting (and may fire non-mutating listeners after this).
Example
This example registers a listener that is called at the end of the transaction, just before its listeners will be called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched and valuesTouched parameters in the listener work.
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
})
.setValues({open: true, employees: 3});
const listenerId = store.addWillFinishTransactionListener(
(store, _, getTransactionLog) => {
const {cellsTouched, valuesTouched} = getTransactionLog?.() ?? {};
console.log(`Cells/Values touched: ${cellsTouched}/${valuesTouched}`);
},
);
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
const listenerId3 = store.addValuesListener(() =>
console.log('Values changed'),
);
store.transaction(() =>
store.setCell('pets', 'fido', 'color', 'brown').setValue('employees', 3),
);
// -> 'Cells/Values touched: false/false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Cells/Values touched: true/false'
// -> 'Tables changed'
store.transaction(() => store.setValue('employees', 4));
// -> 'Cells/Values touched: false/true'
// -> 'Values changed'
store.transaction(() => {
store
.setRow('pets', 'felix', {species: 'cat'})
.delRow('pets', 'felix')
.setValue('city', 'London')
.delValue('city');
});
// -> 'Cells/Values touched: true/true'
// But no Tables or Values listeners fired since there are no net changes.
store.transaction(
() =>
store
.setRow('pets', 'felix', {species: 'cat'})
.setValue('city', 'London'),
() => true,
);
// -> 'Cells/Values touched: false/false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells/Values touched: undefined/undefined'
// It is meaningless to call this listener directly.
store
.delListener(listenerId)
.delListener(listenerId2)
.delListener(listenerId3);
Since
v1.3.0
callListener
The callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed.
callListener(listenerId: string): StoreThis is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.
Examples
This example registers a listener that ensures a Cell has one of list of a valid values. After that list changes, the listener is called to apply the condition to the existing data.
const validColors = ['walnut', 'brown', 'black'];
const store = createStore();
const listenerId = store.addCellListener(
'pets',
null,
'color',
(store, tableId, rowId, cellId, color) => {
if (!validColors.includes(color)) {
store.setCell(tableId, rowId, cellId, validColors[0]);
}
},
true,
);
store.setRow('pets', 'fido', {species: 'dog', color: 'honey'});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'walnut'}
validColors.shift();
console.log(validColors);
// -> ['brown', 'black']
store.callListener(listenerId);
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}
store.delListener(listenerId);
This example registers a listener to Row Id changes. It is explicitly called and fires for two Tables in the Store.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
console.log(`Row Ids listener called for ${tableId} table`);
});
store.callListener(listenerId);
// -> 'Row Ids listener called for pets table'
// -> 'Row Ids listener called for species table'
store.delListener(listenerId);
This example registers a listener Value changes. It is explicitly called and fires for two Values in the Store.
const store = createStore().setValues({open: true, employees: 3});
const listenerId = store.addValueListener(
null,
(store, valueId, value) => {
console.log(`Value listener called for ${valueId} value, ${value}`);
},
);
store.callListener(listenerId);
// -> 'Value listener called for open value, true'
// -> 'Value listener called for employees value, 3'
store.delListener(listenerId);
This example registers listeners for the end of transactions, and for invalid Cells. They are explicitly called, meaninglessly. The former receives empty arguments. The latter is not called at all.
const store = createStore();
const listenerId = store.addWillFinishTransactionListener(
(store, cellsTouched, valuesTouched) => {
console.log(`Transaction finish: ${cellsTouched}/${valuesTouched}`);
},
);
store.callListener(listenerId);
// -> 'Transaction finish: undefined/undefined'
store.delListener(listenerId);
const listenerId2 = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log('Invalid cell', tableId, rowId, cellId);
},
);
store.callListener(listenerId2);
// -> undefined
store.delListener(listenerId2);
delListener
The delListener method removes a listener that was previously added to the Store.
delListener(listenerId: string): Store| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Store | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Store may re-use this Id for future listeners added to it.
Example
This example registers a listener and then removes it.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(() => {
console.log('Tables changed');
});
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
store.delListener(listenerId);
store.setCell('pets', 'fido', 'color', 'honey');
// -> undefined
// The listener is not called.
Iterator methods
This is the collection of iterator methods within the Store interface. There are 5 iterator methods in total.
forEachTable
The forEachTable method takes a function that it will then call for each Table in the Store.
forEachTable(tableCallback: TableCallback): void| Type | Description | |
|---|---|---|
tableCallback | TableCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Table structure of the Store in a functional style. The tableCallback parameter is a TableCallback function that will be called with the Id of each Table, and with a function that can then be used to iterate over each Row of the Table, should you wish.
Example
This example iterates over each Table in a Store, and lists each Row Id within them.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.forEachTable((tableId, forEachRow) => {
console.log(tableId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'pets'
// -> '- fido'
// -> 'species'
// -> '- dog'
forEachTableCell
The forEachTableCell method takes a function that it will then call for each Cell used across the whole Table.
forEachTableCell(
tableId: string,
tableCellCallback: TableCellCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
tableCellCallback | TableCellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Cell structure of the Table in a functional style. The tableCellCallback parameter is a TableCellCallback function that will be called with the Id of each Cell and the count of Rows in the Table in which it appears.
Example
This example iterates over each Cell Id used across the whole Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat', legs: 4}},
});
store.forEachTableCell('pets', (cellId, count) => {
console.log(`${cellId}: ${count}`);
});
// -> 'species: 2'
// -> 'legs: 1'
Since
v3.3.0
forEachRow
The forEachRow method takes a function that it will then call for each Row in a specified Table.
forEachRow(
tableId: string,
rowCallback: RowCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
rowCallback | RowCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Row structure of the Table in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id of each Row, and with a function that can then be used to iterate over each Cell of the Row, should you wish.
Example
This example iterates over each Row in a Table, and lists each Cell Id within them.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {color: 'black'},
},
});
store.forEachRow('pets', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- species'
// -> 'felix'
// -> '- color'
forEachCell
The forEachCell method takes a function that it will then call for each Cell in a specified Row.
forEachCell(
tableId: string,
rowId: string,
cellCallback: CellCallback,
): void| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellCallback | CellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Cell structure of the Row in a functional style. The cellCallback parameter is a CellCallback function that will be called with the Id and value of each Cell.
Example
This example iterates over each Cell in a Row, and lists its value.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.forEachCell('pets', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
forEachValue
The forEachValue method takes a function that it will then call for each Value in a Store.
forEachValue(valueCallback: ValueCallback): void| Type | Description | |
|---|---|---|
valueCallback | ValueCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Value structure of the Store in a functional style. The valueCallback parameter is a ValueCallback function that will be called with the Id and value of each Value.
Example
This example iterates over each Value in a Store, and lists its value.
const store = createStore().setValues({open: true, employees: 3});
store.forEachValue((valueId, value) => {
console.log(`${valueId}: ${value}`);
});
// -> 'open: true'
// -> 'employees: 3'
Since
v3.0.0
Transaction methods
This is the collection of transaction methods within the Store interface. There are only three transaction methods, finishTransaction, startTransaction, and transaction.
finishTransaction
The finishTransaction method allows you to explicitly finish a transaction that has made multiple mutations to the Store, triggering all calls to the relevant listeners.
finishTransaction(doRollback?: DoRollback): Store| Type | Description | |
|---|---|---|
doRollback? | DoRollback | An optional callback that should return |
| returns | Store | A reference to the |
Transactions are useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction methods for you. See that method for several transaction examples.
Use this finishTransaction method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. There must have been a corresponding startTransaction method that this completes, of course, otherwise this function has no effect.
The optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with getTransactionChanges and getTransactionLog parameters, which inform you of the net changes that have been made during the transaction, at different levels of detail. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row listener is only called once.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
This example makes multiple changes to the Store, including some attempts to update a Cell with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob'])
.finishTransaction((_, getTransactionLog) => {
const {changedCells, invalidCells, changedValues, invalidValues} =
getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
});
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Since
v1.3.0
startTransaction
The startTransaction method allows you to explicitly start a transaction that will make multiple mutations to the Store, buffering all calls to the relevant listeners until it completes when you call the finishTransaction method.
startTransaction(): StoreTransactions are useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
Generally it is preferable to use the transaction method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction methods for you. See that method for several transaction examples.
Use this startTransaction method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. You must remember to also call the finishTransaction method explicitly when it is done, of course.
Example
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. In the second case, the Row listener is only called once.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store
.startTransaction()
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true)
.finishTransaction();
// -> 'Fido changed'
Since
v1.3.0
transaction
The transaction method takes a function that makes multiple mutations to the Store, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: DoRollback,
): Return| Type | Description | |
|---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | DoRollback | An optional callback that should return |
| returns | Return | Whatever value the provided transaction function returns. |
This method is useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.
If multiple changes are made to a piece of Store data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell had a value 'a' and then, within a transaction, it was changed to 'b' and then 'c', any CellListener registered for that cell would be called once as if there had been a single change from 'a' to 'c'.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback is a DoRollback callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with getTransactionChanges and getTransactionLog parameters, which inform you of the net changes that have been made during the transaction, at different levels of detail. See the DoRollback documentation for more details.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row listener is only called once.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'walnut')
.setCell('pets', 'fido', 'sold', true),
);
// -> 'Fido changed'
This example makes multiple changes to one Cell. The Cell listener is called once - and with the final value - only if there is a net overall change.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'brown')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> 'walnut'
store.transaction(() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'color', 'walnut'),
);
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store, including some attempts to update a Cell and Value with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
const store = createStore()
.setTables({pets: {fido: {species: 'dog', color: 'brown'}}})
.setValues({open: true});
store.transaction(
() =>
store
.setCell('pets', 'fido', 'color', 'black')
.setCell('pets', 'fido', 'eyes', ['left', 'right'])
.setCell('pets', 'fido', 'info', {sold: null})
.setValue('open', false)
.setValue('employees', ['alice', 'bob']),
(_, getTransactionLog) => {
const {changedCells, invalidCells, changedValues, invalidValues} =
getTransactionLog();
console.log(store.getTables());
console.log(changedCells);
console.log(invalidCells);
console.log(changedValues);
console.log(invalidValues);
return invalidCells['pets'] != null;
},
);
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
// -> {pets: {fido: {color: ['brown', 'black']}}}
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
// -> {open: [true, false]}
// -> {employees: [['alice', 'bob']]}
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
console.log(store.getValues());
// -> {open: true}
Deleter methods
This is the collection of deleter methods within the Store interface. There are 9 deleter methods in total.
delTables
The delTables method lets you remove all of the data in a Store.
delTables(): StoreExample
This example removes the data of a Store.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.delTables();
console.log(store.getTables());
// -> {}
delTablesSchema
The delTablesSchema method lets you remove the TablesSchema of the Store.
delTablesSchema(): StoreExample
This example removes the TablesSchema of a Store.
const store = createStore().setTablesSchema({
pets: {species: {type: 'string'}},
});
store.delTablesSchema();
console.log(store.getTablesSchemaJson());
// -> '{}'
delTable
The delTable method lets you remove a single Table from the Store.
delTable(tableId: string): StoreExample
This example removes a Table from a Store.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
store.delTable('pets');
console.log(store.getTables());
// -> {species: {dog: {price: 5}}}
delRow
The delRow method lets you remove a single Row from a Table.
delRow(
tableId: string,
rowId: string,
): StoreIf this is the last Row in its Table, then that Table will be removed.
Example
This example removes a Row from a Table.
const store = createStore().setTables({
pets: {fido: {species: 'dog'}, felix: {species: 'cat'}},
});
store.delRow('pets', 'fido');
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
delCell
The delCell method lets you remove a single Cell from a Row.
delCell(
tableId: string,
rowId: string,
cellId: string,
forceDel?: boolean,
): Store| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
| returns | Store | A reference to the |
When there is no TablesSchema applied to the Store, then if this is the last Cell in its Row, then that Row will be removed. If, in turn, that is the last Row in its Table, then that Table will be removed.
If there is a TablesSchema applied to the Store and it specifies a default value for this Cell, then deletion will result in it being set back to its default value. To override this, use the forceDel parameter, as described below.
The forceDel parameter is an optional flag that is only relevant if a TablesSchema provides a default value for this Cell. Under such circumstances, deleting a Cell value will normally restore it to the default value. If this flag is set to true, the complete removal of the Cell is instead guaranteed. But since doing do so would result in an invalid Row (according to the TablesSchema), in fact the whole Row is deleted to retain the integrity of the Table. Therefore, this flag should be used with caution.
Examples
This example removes a Cell from a Row without a TablesSchema.
const store = createStore().setTables({
pets: {fido: {species: 'dog', sold: true}},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example removes a Cell from a Row with a TablesSchema that defaults its value.
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
This example removes a Cell from a Row with a TablesSchema that defaults its value, but uses the forceDel parameter to override it.
const store = createStore()
.setTables({
pets: {fido: {species: 'dog', sold: true}, felix: {species: 'cat'}},
})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
store.delCell('pets', 'fido', 'sold', true);
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat', sold: false}}}
delValues
The delValues method lets you remove all the Values from a Store.
delValues(): StoreIf there is a ValuesSchema applied to the Store and it specifies a default value for any Value Id, then deletion will result in it being set back to its default value.
Examples
This example removes all Values from a Store without a ValuesSchema.
const store = createStore().setValues({open: true, employees: 3});
store.delValues();
console.log(store.getValues());
// -> {}
This example removes all Values from a Store with a ValuesSchema that defaults one of its values.
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValues();
console.log(store.getValues());
// -> {open: false}
Since
v3.0.0
delValuesSchema
The delValuesSchema method lets you remove the ValuesSchema of the Store.
delValuesSchema(): StoreExample
This example removes the ValuesSchema of a Store.
const store = createStore().setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delValuesSchema();
console.log(store.getValuesSchemaJson());
// -> '{}'
Since
v3.0.0
delValue
The delValue method lets you remove a single Value from a Store.
delValue(valueId: string): StoreIf there is a ValuesSchema applied to the Store and it specifies a default value for this Value Id, then deletion will result in it being set back to its default value.
Examples
This example removes a Value from a Store without a ValuesSchema.
const store = createStore().setValues({open: true, employees: 3});
store.delValue('employees');
console.log(store.getValues());
// -> {open: true}
This example removes a Value from a Store with a ValuesSchema that defaults its value.
const store = createStore()
.setValues({open: true, employees: 3})
.setValuesSchema({
open: {type: 'boolean', default: false},
employees: {type: 'number'},
});
store.delValue('open');
console.log(store.getValues());
// -> {open: false, employees: 3}
Since
v3.0.0
delSchema
The delSchema method lets you remove both the TablesSchema and ValuesSchema of the Store.
delSchema(): StorePrior to v3.0, this method removed the TablesSchema only.
Example
This example removes the TablesSchema and ValuesSchema of a Store.
const store = createStore()
.setTablesSchema({
pets: {species: {type: 'string'}},
})
.setValuesSchema({
sold: {type: 'boolean', default: false},
});
store.delSchema();
console.log(store.getSchemaJson());
// -> '[{},{}]'
Since
v3.0.0
Development methods
This is the collection of development methods within the Store interface. There is only one method, getListenerStats.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Store, and is used for debugging purposes.
getListenerStats(): StoreListenerStats| returns | StoreListenerStats | A |
|---|
The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners.
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a small and simple Store.
const store = createStore();
store.addTablesListener(() => console.log('Tables changed'));
store.addRowIdsListener(() => console.log('Row Ids changed'));
const listenerStats = store.getListenerStats();
console.log(listenerStats.rowIds);
// -> 1
console.log(listenerStats.tables);
// -> 1
Functions
There is one function, createStore, within the store module.
createStore
The createStore function creates a Store, and is the main entry point into the store module.
createStore(): StoreSince (or perhaps because) it is the most important function in the whole module, it is trivially simple.
Examples
This example creates a Store.
const store = createStore();
console.log(store.getTables());
// -> {}
This example creates a Store with some initial data:
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
This example creates a Store with some initial data and a TablesSchema:
const store = createStore()
.setTables({pets: {fido: {species: 'dog'}}})
.setTablesSchema({
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
See also
The Basics guides
Type Aliases
These are the type aliases within the store module.
Listener type aliases
This is the collection of listener type aliases within the store module. There are 21 listener type aliases in total.
TablesListener
The TablesListener type describes a function that is used to listen to changes to the whole Store.
(
store: Store,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A TablesListener is provided when using the addTablesListener method. See that method for specific examples.
When called, a TablesListener is given a reference to the Store and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.
TableIdsListener
The TableIdsListener type describes a function that is used to listen to changes to the Table Ids in the Store.
(
store: Store,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A TableIdsListener is provided when using the addTableIdsListener method. See that method for specific examples.
When called, a TableIdsListener is given a reference to the Store.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
TableCellIdsListener
The TableCellIdsListener type describes a function that is used to listen to changes to the Cell Ids that appear anywhere in a Table.
(
store: Store,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A TableCellIdsListener is provided when using the addTableCellIdsListener method. See that method for specific examples.
When called, a TableCellIdsListener is given a reference to the Store, the Id of the Table whose Cell Ids changed, and a GetIdChanges function that can be used to query which Ids changed during the transaction.
Since
v3.3.0
TableListener
The TableListener type describes a function that is used to listen to changes to a Table.
(
store: Store,
tableId: Id,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A TableListener is provided when using the addTableListener method. See that method for specific examples.
When called, a TableListener is given a reference to the Store, the Id of the Table that changed, and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.
RowIdsListener
The RowIdsListener type describes a function that is used to listen to changes to the Row Ids in a Table.
(
store: Store,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A RowIdsListener is provided when using the addRowIdsListener method. See that method for specific examples.
When called, a RowIdsListener is given a reference to the Store, and the Id of the Table whose Row Ids changed.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
SortedRowIdsListener
The SortedRowIdsListener type describes a function that is used to listen to changes to sorted Row Ids in a Table.
(
store: Store,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
cellId | Id | undefined | |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
| returns | void | This has no return value. |
A SortedRowIdsListener is provided when using the addSortedRowIdsListener method. See that method for specific examples.
When called, a SortedRowIdsListener is given a reference to the Store, the Id of the Table whose Row Ids sorting changed, the Cell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Since
v2.0.0
RowCountListener
The RowCountListener type describes a function that is used to listen to changes to the number of Row objects in a Table.
(
store: Store,
tableId: Id,
count: number,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
count | number | |
| returns | void | This has no return value. |
A RowCountListener is provided when using the addRowCountListener method. See that method for specific examples.
When called, a RowCountListener is given a reference to the Store, the Id of the Table whose Row Ids changed, and the number of Row objects in the Table.
Since
v4.1.0
RowListener
The RowListener type describes a function that is used to listen to changes to a Row.
(
store: Store,
tableId: Id,
rowId: Id,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A RowListener is provided when using the addRowListener method. See that method for specific examples.
When called, a RowListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.
CellIdsListener
The CellIdsListener type describes a function that is used to listen to changes to the Cell Ids in a Row.
(
store: Store,
tableId: Id,
rowId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A CellIdsListener is provided when using the addCellIdsListener method. See that method for specific examples.
When called, a CellIdsListener is given a reference to the Store, the Id of the Table that changed, and the Id of the Row whose Cell Ids changed.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
CellChange
The CellChange type describes a Cell's changes during a transaction.
[changed: boolean, oldCell: CellOrUndefined, newCell: CellOrUndefined]This is returned by the GetCellChange function that is provided to every listener when called. This array contains the previous value of a Cell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
CellListener
The CellListener type describes a function that is used to listen to changes to a Cell.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: Cell,
oldCell: Cell,
getCellChange: GetCellChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
newCell | Cell | The new value of the |
oldCell | Cell | The old value of the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A CellListener is provided when using the addCellListener method. See that method for specific examples.
When called, a CellListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and the Id of Cell that changed. It is also given the new value of the Cell, the old value of the Cell, and a GetCellChange function that can be used to query Cell values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present and the new and old values of the Cell will be the same.
GetCellChange
The GetCellChange type describes a function that returns information about any Cell's changes during a transaction.
(
tableId: Id,
rowId: Id,
cellId: Id,
): CellChange| Type | Description | |
|---|---|---|
tableId | Id | |
rowId | Id | |
cellId | Id | |
| returns | CellChange | A |
A GetCellChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a Cell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
InvalidCellListener
The InvalidCellListener type describes a function that is used to listen to attempts to set invalid data to a Cell.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
invalidCells: any[],
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
invalidCells | any[] | An array of the values of the |
| returns | void | This has no return value. |
An InvalidCellListener is provided when using the addInvalidCellListener method. See that method for specific examples.
When called, an InvalidCellListener is given a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that was being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.
Since
v1.1.0
ValuesListener
The ValuesListener type describes a function that is used to listen to changes to all the Values in a Store.
(
store: Store,
getValueChange: GetValueChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getValueChange | GetValueChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A ValuesListener is provided when using the addValuesListener method. See that method for specific examples.
When called, a ValuesListener is given a reference to the Store and a GetValueChange function that can be used to query Values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetValueChange function will not be present.
GetValueChange
The GetValueChange type describes a function that returns information about any Value's changes during a transaction.
(valueId: Id): ValueChange| Type | Description | |
|---|---|---|
valueId | Id | |
| returns | ValueChange | A |
A GetValueChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a Value before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
InvalidValueListener
The InvalidValueListener type describes a function that is used to listen to attempts to set invalid data to a Value.
(
store: Store,
valueId: Id,
invalidValues: any[],
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
valueId | Id | |
invalidValues | any[] | An array of the |
| returns | void | This has no return value. |
An InvalidValueListener is provided when using the addInvalidValueListener method. See that method for specific examples.
When called, an InvalidValueListener is given a reference to the Store and the Id of Value that was being attempted to be changed. It is also given the invalid value of the Value, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Value within a single transaction, this is an array containing each attempt, chronologically.
Since
v3.0.0
ValueChange
The ValueChange type describes a Value's changes during a transaction.
[changed: boolean, oldValue: ValueOrUndefined, newValue: ValueOrUndefined]This is returned by the GetValueChange function that is provided to every listener when called. This array contains the previous value of a Value before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
ValueIdsListener
The ValueIdsListener type describes a function that is used to listen to changes to the Value Ids in a Store.
(
store: Store,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getIdChanges | GetIdChanges | undefined | A function that returns information about the |
| returns | void | This has no return value. |
A ValueIdsListener is provided when using the addValueIdsListener method. See that method for specific examples.
When called, a ValueIdsListener is given a reference to the Store.
Since v3.3, the listener is also passed a GetIdChanges function that can be used to query which Ids changed during the transaction.
ValueListener
The ValueListener type describes a function that is used to listen to changes to a Value.
(
store: Store,
valueId: Id,
newValue: Value,
oldValue: Value,
getValueChange: GetValueChange | undefined,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
valueId | Id | |
newValue | Value | The new value of the |
oldValue | Value | The old value of the |
getValueChange | GetValueChange | undefined | A function that returns information about any |
| returns | void | This has no return value. |
A ValueListener is provided when using the addValueListener method. See that method for specific examples.
When called, a ValueListener is given a reference to the Store and the Id of Value that changed. It is also given the new value of the Value, the old value of the Value, and a GetValueChange function that can be used to query Values before and after the current transaction.
Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetValueChange function will not be present and the new and old values of the Value will be the same.
Since
v3.0.0
GetIdChanges
The GetIdChanges type describes a function that returns information about the changes to a set of Ids during a transaction.
(): {[id: Id]: 1 | -1}| returns | {[id: Id]: 1 | -1} | An object keyed by |
|---|
A GetIdChanges function is provided to every listener when called due Ids in the Store changing. It returns an object where each key is an Id that changed. The listener can then easily identify which Ids have been added (those with the value 1), and which have been removed (those with the value -1).
Since
v3.3.0
TransactionListener
The TransactionListener type describes a function that is used to listen to the completion of a transaction for the Store.
(
store: Store,
getTransactionChanges: GetTransactionChanges,
getTransactionLog: GetTransactionLog,
): void| Type | Description | |
|---|---|---|
store | Store | A reference to the |
getTransactionChanges | GetTransactionChanges | A function to be called to get the changes made to the |
getTransactionLog | GetTransactionLog | |
| returns | void | This has no return value. |
A TransactionListener is provided when using the addWillFinishTransactionListener and addDidFinishTransactionListener methods. See those methods for specific examples.
When called, a TransactionListener is simply given a reference to the Store and, since v4.0, two functions that you can call to get information about the changes made within the transaction. See the GetTransactionChanges and GetTransactionLog function types for more details.
Callback type aliases
This is the collection of callback type aliases within the store module. There are 9 callback type aliases in total.
TableCallback
The TableCallback type describes a function that takes a Table's Id and a callback to loop over each Row within it.
(
tableId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void| Type | Description | |
|---|---|---|
tableId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A TableCallback is provided when using the forEachTable method, so that you can do something based on every Table in the Store. See that method for specific examples.
TableCellCallback
The TableCellCallback type describes a function that takes a Cell's Id and the count of times it appears across a whole Table.
(
cellId: Id,
count: number,
): void| Type | Description | |
|---|---|---|
cellId | Id | |
count | number | |
| returns | void | This has no return value. |
A TableCellCallback is provided when using the forEachTableCell method, so that you can do something based on every Cell used across a Table. See that method for specific examples.
RowCallback
The RowCallback type describes a function that takes a Row's Id and a callback to loop over each Cell within it.
(
rowId: Id,
forEachCell: (cellCallback: CellCallback) => void,
): void| Type | Description | |
|---|---|---|
rowId | Id | |
forEachCell | (cellCallback: CellCallback) => void | |
| returns | void | This has no return value. |
A RowCallback is provided when using the forEachRow method, so that you can do something based on every Row in a Table. See that method for specific examples.
CellCallback
The CellCallback type describes a function that takes a Cell's Id and its value.
(
cellId: Id,
cell: Cell,
): voidA CellCallback is provided when using the forEachCell method, so that you can do something based on every Cell in a Row. See that method for specific examples.
GetCell
The GetCell type describes a function that takes a Id and returns the Cell value for a particular Row.
(cellId: Id): CellOrUndefined| Type | Description | |
|---|---|---|
cellId | Id | |
| returns | CellOrUndefined |
A GetCell can be provided when setting definitions, as in the setMetricDefinition method of a Metrics object, or the setIndexDefinition method of an Indexes object. See those methods for specific examples.
MapCell
The MapCell type describes a function that takes an existing Cell value and returns another.
(cell: CellOrUndefined): Cell| Type | Description | |
|---|---|---|
cell | CellOrUndefined | The current value of the |
| returns | Cell |
A MapCell can be provided in the setCell method to map an existing value to a new one, such as when incrementing a number. See that method for specific examples.
MapValue
The MapValue type describes a function that takes an existing Value and returns another.
(value: ValueOrUndefined): Value| Type | Description | |
|---|---|---|
value | ValueOrUndefined | |
| returns | Value |
A MapValue can be provided in the setValue method to map an existing Value to a new one, such as when incrementing a number. See that method for specific examples.
Since
v3.0.0
ValueCallback
The ValueCallback type describes a function that takes a Value's Id and its actual value.
(
valueId: Id,
value: Value,
): voidA ValueCallback is provided when using the forEachValue method, so that you can do something based on every Value in a Store. See that method for specific examples.
Since
v3.0.0
DoRollback
The DoRollback type describes a function that you can use to rollback the transaction if it did not complete to your satisfaction.
(
getTransactionChanges: GetTransactionChanges,
getTransactionLog: GetTransactionLog,
): boolean| Type | Description | |
|---|---|---|
getTransactionChanges | GetTransactionChanges | A function to be called to get the changes made to the |
getTransactionLog | GetTransactionLog | |
| returns | boolean |
A DoRollback can be provided when calling the transaction method or the finishTransaction method. See those methods for specific examples.
Since v4.0, this function is provided only with two functions that you can call to get information about the changes made within the transaction (in order to decide whether to do the rollback). See the GetTransactionChanges and GetTransactionLog function types for more details.
Schema type aliases
This is the collection of schema type aliases within the store module. There are 10 schema type aliases in total.
TablesSchema
The TablesSchema type describes the tabular structure of a Store in terms of valid Table Ids and the types of Cell that can exist within them.
{[tableId: Id]: {[cellId: Id]: CellSchema}}A TablesSchema comprises a JavaScript object describing each Table, in turn a nested JavaScript object containing information about each Cell and its CellSchema. It is provided to the setTablesSchema method.
Example
When applied to a Store, this TablesSchema only allows one Table called pets, in which each Row may contain a string species Cell, and is guaranteed to contain a boolean sold Cell that defaults to false.
const tableSchema: TablesSchema = {
pets: {
species: {type: 'string'},
sold: {type: 'boolean', default: false},
},
};
NoTablesSchema
The NoTablesSchema type is a TablesSchema-like type for when one has not been provided.
{[tableId: Id]: {[cellId: Id]: {type: "any"}}}OptionalTablesSchema
The OptionalTablesSchema type is used by generic types that can optionally take a TablesSchema.
TablesSchema | NoTablesSchemaCellSchema
The CellSchema type describes what values are allowed for each Cell in a Table.
{
type: "string";
default?: string;
} | {
type: "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
}A CellSchema specifies the type of the Cell (string, boolean, or number), and what the default value can be when an explicit value is not specified.
If a default value is provided (and its type is correct), you can be certain that that Cell will always be present in a Row.
If the default value is not provided (or its type is incorrect), the Cell may be missing from the Row, but when present you can be guaranteed it is of the correct type.
Example
When applied to a Store, this CellSchema ensures a boolean Cell is always present, and defaults it to false.
const requiredBoolean: CellSchema = {type: 'boolean', default: false};
ValuesSchema
The ValuesSchema type describes the keyed Values that can be set in a Store and their types.
{[valueId: Id]: ValueSchema}A ValuesSchema comprises a JavaScript object describing each Value and its ValueSchema. It is provided to the setValuesSchema method.
Example
When applied to a Store, this ValuesSchema only allows one boolean Value called open, that defaults to false.
const valuesSchema: ValuesSchema = {
open: {type: 'boolean', default: false},
};
Since
v3.0.0
NoValuesSchema
The NoValuesSchema type is a ValuesSchema-like type for when one has not been provided.
{[valueId: Id]: {type: "any"}}OptionalValuesSchema
The OptionalValuesSchema type is used by generic types that can optionally take a ValuesSchema.
ValuesSchema | NoValuesSchemaValueSchema
The ValueSchema type describes what values are allowed for keyed Values in a Store.
{
type: "string";
default?: string;
} | {
type: "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
}A ValueSchema specifies the type of the Value (string, boolean, or number), and what the default value can be when an explicit value is not specified.
If a default value is provided (and its type is correct), you can be certain that the Value will always be present in a Store.
If the default value is not provided (or its type is incorrect), the Value may not be present in the Store, but when present you can be guaranteed it is of the correct type.
Example
When applied to a Store, this ValueSchema ensures a boolean Value is always present, and defaults it to false.
const requiredBoolean: ValueSchema = {type: 'boolean', default: false};
Since
v3.0.0
NoSchemas
The NoSchemas type is used as a default by generic types that can optionally take either or both of a TablesSchema and ValuesSchema.
[NoTablesSchema, NoValuesSchema]OptionalSchemas
The OptionalSchemas type is used by generic types that can optionally take either or both of a TablesSchema and ValuesSchema.
[OptionalTablesSchema, OptionalValuesSchema]Store type aliases
This is the collection of store type aliases within the store module. There are 8 store type aliases in total.
Tables
The Tables type is the data structure representing all of the data in a Store.
{[tableId: Id]: Table}A Tables object is used when setting all of the tables together with the setTables method, and when getting them back out again with the getTables method. A Tables object is a regular JavaScript object containing individual Table objects, keyed by their Id.
Example
const tables: Tables = {
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
},
species: {
dog: {price: 5},
cat: {price: 4},
},
};
Table
The Table type is the data structure representing the data in a single table.
{[rowId: Id]: Row}A Table is used when setting a table with the setTable method, and when getting it back out again with the getTable method. A Table object is a regular JavaScript object containing individual Row objects, keyed by their Id.
Example
const table: Table = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
Row
The Row type is the data structure representing the data in a single row.
{[cellId: Id]: Cell}A Row is used when setting a row with the setRow method, and when getting it back out again with the getRow method. A Row object is a regular JavaScript object containing individual Cell objects, keyed by their Id.
Example
const row: Row = {species: 'dog', color: 'brown'};
Cell
The Cell type is the data structure representing the data in a single cell.
string | number | booleanA Cell is used when setting a cell with the setCell method, and when getting it back out again with the getCell method. A Cell is a JavaScript string, number, or boolean.
Example
const cell: Cell = 'dog';
CellOrUndefined
The CellOrUndefined type is a data structure representing the data in a single cell, or the value undefined.
Cell | undefinedThis is used when describing a Cell that is present or that is not present, such as when it has been deleted, or when describing a previous state where the Cell value has since been added.
Values
The Values type is the data structure representing all the keyed values in a Store.
{[valueId: Id]: Value}A Values object is used when setting values with the setValues method, and when getting them back out again with the getValues method. A Values object is a regular JavaScript object containing individual Value objects, keyed by their Id.
Example
const values: Values = {open: true, employees: 4};
Since
v3.0.0
Value
The Value type is the data structure representing the data in a single keyed value.
string | number | booleanA Value is used when setting a value with the setValue method, and when getting it back out again with the getValue method. A Value is a JavaScript string, number, or boolean.
Example
const value: Value = 'dog';
Since
v3.0.0
ValueOrUndefined
The ValueOrUndefined type is a data structure representing the data in a single value, or the value undefined.
Value | undefinedThis is used when describing a Value that is present or that is not present, such as when it has been deleted, or when describing a previous state where the Value has since been added.
Since
v3.0.0
Transaction type aliases
This is the collection of transaction type aliases within the store module. There are 15 transaction type aliases in total.
ChangedTableIds
The ChangedTableIds type describes the Table Ids that were added or removed during a transaction.
{[tableId: Id]: IdAddedOrRemoved}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a simple object that has Table Id as key, and an IdAddedOrRemoved number indicating whether the Table Id was added (1) or removed (-1).
Note that there will be no entry if the content of the Table itself changed. For that you should consult the ChangedRowIds, ChangedCellIds, or ChangedCells types.
Since
v4.0.0
ChangedRowIds
The ChangedRowIds type describes the Row Ids that were added or removed during a transaction.
{[tableId: Id]: {[rowId: Id]: IdAddedOrRemoved}}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a nested object that has Table Id as a top-level key, and then Row Id as an inner key. The values of the inner objects are IdAddedOrRemoved numbers indicating whether the Row Id was added (1) or removed (-1) to the given Table.
Note that there will be no entry if the content of the Row itself changed. For that you should consult the ChangedCellIds or ChangedCells types.
Since
v4.0.0
ChangedCellIds
The ChangedCellIds type describes the Cell Ids that were added or removed during a transaction.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: IdAddedOrRemoved}}}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a nested object that has Table Id as a top-level key, and Row Id, and then CellId as inner keys. The values of the inner objects are IdAddedOrRemoved numbers indicating whether the Cell Id was added (1) or removed (-1) to the given Row.
Note that there will be no entry if the content of the Cell itself changed. For that you should consult the ChangedCells type.
Since
v4.0.0
ChangedCell
The ChangedCell type describes a Cell that has been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
[CellOrUndefined, CellOrUndefined]It provides both the old and new Cell values in a two-part array. These are describing the state of the changed Cell in the Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Cell was added during the transaction. An undefined value for the second item in the array means that the Cell was removed during the transaction. An array with two different Cell values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Cell was changed to a different value and then changed back.
Since
v1.2.0
ChangedCells
The ChangedCells type describes the Cell values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: ChangedCell}}}A ChangedCells object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is a nested structure of Table, Row, and Cell objects, much like the Tables object, but one which provides both the old and new Cell values in a two-part array. These are describing the state of each changed Cell in Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Cell was added during the transaction. An undefined value for the second item in the array means that the Cell was removed during the transaction. An array with two different Cell values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Cell was changed to a different value and then changed back.
Since
v1.2.0
InvalidCells
The InvalidCells type describes the invalid Cell values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: any[]}}}An InvalidCells object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is a nested structure of Table, Row, and Cell objects, much like the Tables object, but one for which Cell values are listed in array (much like the InvalidCellListener type) so that multiple failed attempts to change a Cell during the transaction are described.
Since
v1.2.0
ChangedValues
The ChangedValues type describes the Values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[valueId: Id]: ChangedValue}A ChangedValues object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is an object containing the old and new Values in two-part arrays. These are describing the state of each changed Value in Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Value was added during the transaction. An undefined value for the second item in the array means that the Value was removed during the transaction. An array with two different Values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Value was changed to a different value and then changed back.
Since
v3.0.0
InvalidValues
The InvalidValues type describes the invalid Values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[valueId: Id]: any[]}An InvalidValues object is provided to the doRollback callback when using the transaction method and the finishTransaction method. See those methods for specific examples.
This type is an object containing each invalid Value's attempt listed in array (much like the InvalidValueListener type) so that multiple failed attempts to change a Value during the transaction are described.
Since
v3.0.0
ChangedValue
The ChangedValue type describes a Value that has been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
[ValueOrUndefined, ValueOrUndefined]It provides both the the old and new Values in a two-part array. These describe the state of the changed Value in the Store at the start of the transaction, and by the end of the transaction.
Hence, an undefined value for the first item in the array means that the Value was added during the transaction. An undefined value for the second item in the array means that the Value was removed during the transaction. An array with two different Values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Value was changed to a different value and then changed back.
Since
v3.0.0
ChangedValueIds
The ChangedValueIds type describes the Value Ids that were added or removed during a transaction.
{[valueId: Id]: IdAddedOrRemoved}It is available to the DoRollback function and to a TransactionListener function via the TransactionLog object.
It is a simple object that has Value Id as key, and an IdAddedOrRemoved number indicating whether the Value Id was added (1) or removed (-1).
Note that there will be no entry if the content of the Value itself changed. For that you should consult the ChangedValues type.
Since
v4.0.0
GetTransactionChanges
The GetTransactionChanges type describes a function that returns the net meaningful changes that were made to a Store during a transaction.
(): TransactionChanges| returns | TransactionChanges |
|---|
It is provided to the DoRollback callback and to a TransactionListener listener when a transaction completes. See the TransactionChanges type for more information and an example of the returned data structure.
Since
v4.0.0
GetTransactionLog
The GetTransactionLog type describes a function that returns the changes that were made to a Store during a transaction in detail.
(): TransactionLog| returns | TransactionLog |
|---|
It is provided to the DoRollback callback and to a TransactionListener listener when a transaction completes. See the TransactionLog type for more information.
Since
v4.0.0
IdAddedOrRemoved
The IdAddedOrRemoved type describes a change made to an Id in either the tabular of keyed-value part of the Store.
1 | -1This type is used in other types like ChangedTableIds, ChangedRowIds, ChangedCellIds, and ChangedValueIds.
It is a simple number: a 1 indicates that a given Id was added to the Store during a transaction, and a -1 indicates that it was removed.
Since
v4.0.0
TransactionChanges
The TransactionChanges type describes the net meaningful changes that were made to a Store during a transaction.
[{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: Cell | null} | null} | null}, {[valueId: Id]: Value | null}]This contains mostly equivalent information to a TransactionLog, but in a form that can be more efficiently parsed and serialized (for example in the case of synchronization between systems).
It is an array of two objects, representing tabular and keyed value changes. If the first item is an empty object, it means no tabular changes were made. If the second item is an empty object, it means no keyed value changes were made.
If not empty, the first object has an entry for each Table in a Store that has had a change within it. If the entry is null, it means that whole Table was deleted. Otherwise, the entry will be an object with an entry for each Row in that Table that had a change within it. In turn, if that entry is null, it means the Row was deleted. Otherwise, the entry will be an object with an entry for each Cell in that Row that had a change within it. If the entry is null, the Cell was deleted, otherwise it will contain the new value the Cell was changed to during the transaction.
If not empty, the second object has an entry for each Value in a Store that has had a change. If the entry is null, the Value was deleted, otherwise it will contain the new Value it was changed to during the transaction.
Example
The following is a valid TransactionChanges array that conveys the following:
[
{ // changes to tabular data in the Store
"pets": { // this Table was changed
"fido": null, // this Row was deleted
"felix": { // this Row was changed
"sold": true, // this Cell was changed
"price": null, // this Cell was deleted
},
},
"pendingSales": null, // this Table was deleted
},
{}, // no changes to keyed value data in the Store
]
Since
v4.0.0
TransactionLog
The TransactionLog type describes the changes that were made to a Store during a transaction in detail.
{
cellsTouched: boolean;
valuesTouched: boolean;
changedCells: ChangedCells;
invalidCells: InvalidCells;
changedValues: ChangedValues;
invalidValues: InvalidValues;
changedTableIds: ChangedTableIds;
changedRowIds: ChangedRowIds;
changedCellIds: ChangedCellIds;
changedValueIds: ChangedValueIds;
}| Type | Description | |
|---|---|---|
cellsTouched | boolean | |
valuesTouched | boolean | |
changedCells | ChangedCells | |
invalidCells | InvalidCells | |
changedValues | ChangedValues | |
invalidValues | InvalidValues | |
changedTableIds | ChangedTableIds | |
changedRowIds | ChangedRowIds | |
changedCellIds | ChangedCellIds | |
changedValueIds | ChangedValueIds |
This contains equivalent information to a TransactionChanges object, but also information about what the previous state of the Store was. The changedCells and changedValues entries contain information about all changes to those parts of the Store, with their before and after values, for example.
cellsTouched and valuesTouched indicate whether Cell or Value data has been touched during the transaction. The two flags are intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell or Value data has either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched and valuesTouched in the listener will be false because all changes have been reverted.
See the documentation for the types of the inner objects for other details.
Since
v4.0.0
Development type aliases
This is the collection of development type aliases within the store module. There is only one type alias, StoreListenerStats.
StoreListenerStats
The StoreListenerStats type describes the number of listeners registered with the Store, and can be used for debugging purposes.
{
tables?: number;
tableIds?: number;
table?: number;
tableCellIds?: number;
rowCount?: number;
rowIds?: number;
sortedRowIds?: number;
row?: number;
cellIds?: number;
cell?: number;
invalidCell?: number;
values?: number;
valueIds?: number;
value?: number;
invalidValue?: number;
transaction?: number;
}| Type | Description | |
|---|---|---|
tables? | number | The number of |
tableIds? | number | The number of |
table? | number | The number of |
tableCellIds? | number | The number of |
rowCount? | number | The number of |
rowIds? | number | The number of |
sortedRowIds? | number | The number of |
row? | number | The number of |
cellIds? | number | The number of |
cell? | number | The number of |
invalidCell? | number | The number of |
values? | number | The number of |
valueIds? | number | The number of |
value? | number | The number of |
invalidValue? | number | The number of |
transaction? | number | The number of |
The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners. A StoreListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.
metrics
The metrics module of the TinyBase project provides the ability to create and track metrics and aggregates of the data in Store objects.
The main entry point to this module is the createMetrics function, which returns a new Metrics object. From there, you can create new Metric definitions, access the values of those Metrics directly, and register listeners for when they change.
Interfaces
There is one interface, Metrics, within the metrics module.
Metrics
A Metrics object lets you define, query, and listen to, aggregations of Cell values within a Table in a Store.
This is useful for counting the number of Row objects in a Table, averaging Cell values, or efficiently performing any arbitrary aggregations.
Create a Metrics object easily with the createMetrics function. From there, you can add new Metric definitions (with the setMetricDefinition method), query their values (with the getMetric method), and add listeners for when they change (with the addMetricListener method).
This module provides a number of predefined and self-explanatory aggregations ('sum', 'avg', 'min', and 'max'), and defaults to counting Row objects when using the setMetricDefinition method. However, far more complex aggregations can be configured with custom functions.
Example
This example shows a very simple lifecycle of a Metrics object: from creation, to adding a definition, getting a Metric, and then registering and removing a listener for it.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'highestPrice', // metricId
'species', // tableId to aggregate
'max', // aggregation
'price', // cellId to aggregate
);
console.log(metrics.getMetric('highestPrice'));
// -> 5
const listenerId = metrics.addMetricListener('highestPrice', () => {
console.log(metrics.getMetric('highestPrice'));
});
store.setCell('species', 'horse', 'price', 20);
// -> 20
metrics.delListener(listenerId);
metrics.destroy();
See also
Metrics And Indexes guides
Rolling Dice demos
Country demo
Todo App demos
Getter methods
This is the collection of getter methods within the Metrics interface. There are 5 getter methods in total.
getStore
The getStore method returns a reference to the underlying Store that is backing this Metrics object.
getStore(): StoreExample
This example creates a Metrics object against a newly-created Store and then gets its reference in order to update its data.
const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
metrics.getStore().setCell('species', 'dog', 'price', 5);
console.log(metrics.getMetric('speciesCount'));
// -> 1
getTableId
The getTableId method returns the Id of the underlying Table that is backing a Metric.
getTableId(metricId: string): undefined | string| Type | Description | |
|---|---|---|
metricId | string | |
| returns | undefined | string |
If the Metric Id is invalid, the method returns undefined.
Example
This example creates a Metrics object, a single Metric definition, and then queries it (and a non-existent definition) to get the underlying Table Id.
const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getTableId('speciesCount'));
// -> 'species'
console.log(metrics.getTableId('petsCount'));
// -> undefined
getMetric
The getMetric method gets the current value of a Metric.
getMetric(metricId: string): undefined | number| Type | Description | |
|---|---|---|
metricId | string | |
| returns | undefined | number | The numeric value of the |
If the identified Metric does not exist (or if the definition references a Table or Cell value that does not exist) then undefined is returned.
Example
This example creates a Store, creates a Metrics object, and defines a simple Metric to average the price values in the Table. It then uses getMetric to access its value (and also the value of a Metric that has not been defined).
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.getMetric('highestPrice'));
// -> 5
console.log(metrics.getMetric('lowestPrice'));
// -> undefined
getMetricIds
The getMetricIds method returns an array of the Metric Ids registered with this Metrics object.
getMetricIds(): IdsExample
This example creates a Metrics object with two definitions, and then gets the Ids of the definitions.
const metrics = createMetrics(createStore())
.setMetricDefinition('speciesCount', 'species')
.setMetricDefinition('petsCount', 'pets');
console.log(metrics.getMetricIds());
// -> ['speciesCount', 'petsCount']
hasMetric
The hasMetric method returns a boolean indicating whether a given Metric exists in the Metrics object, and has a value.
hasMetric(metricId: string): boolean| Type | Description | |
|---|---|---|
metricId | string | |
| returns | boolean |
Example
This example shows two simple Metric existence checks.
const store = createStore();
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.hasMetric('lowestPrice'));
// -> false
console.log(metrics.hasMetric('highestPrice'));
// -> false
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
console.log(metrics.hasMetric('highestPrice'));
// -> true
Listener methods
This is the collection of listener methods within the Metrics interface. There are only three listener methods, addMetricIdsListener, addMetricListener, and delListener.
addMetricIdsListener
The addMetricIdsListener method registers a listener function with the Metrics object that will be called whenever a Metric definition is added or removed.
addMetricIdsListener(listener: MetricIdsListener): string| Type | Description | |
|---|---|---|
listener | MetricIdsListener | The function that will be called whenever a |
| returns | string |
The provided listener is a MetricIdsListener function, and will be called with a reference to the Metrics object.
Example
This example creates a Store, a Metrics object, and then registers a listener that responds to the addition and the removal of a Metric definition.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
const listenerId = metrics.addMetricIdsListener((metrics) => {
console.log(metrics.getMetricIds());
});
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
// -> ['highestPrice']
metrics.delMetricDefinition('highestPrice');
// -> []
metrics.delListener(listenerId);
Since
v4.1.0
addMetricListener
The addMetricListener method registers a listener function with the Metrics object that will be called whenever the value of a specified Metric changes.
addMetricListener(
metricId: IdOrNull,
listener: MetricListener,
): string| Type | Description | |
|---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
| returns | string | A unique |
You can either listen to a single Metric (by specifying the Metric Id as the method's first parameter), or changes to any Metric (by providing a null wildcard).
The provided listener is a MetricListener function, and will be called with a reference to the Metrics object, the Id of the Metric that changed, the new Metric value, and the old Metric value.
Examples
This example creates a Store, a Metrics object, and then registers a listener that responds to any changes to a specific Metric.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const listenerId = metrics.addMetricListener(
'highestPrice',
(metrics, metricId, newMetric, oldMetric) => {
console.log('highestPrice metric changed');
console.log([oldMetric, newMetric]);
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
metrics.delListener(listenerId);
This example creates a Store, a Metrics object, and then registers a listener that responds to any changes to any Metric.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store)
.setMetricDefinition('highestPrice', 'species', 'max', 'price')
.setMetricDefinition('speciesCount', 'species');
const listenerId = metrics.addMetricListener(
null,
(metrics, metricId, newMetric, oldMetric) => {
console.log(`${metricId} metric changed`);
console.log([oldMetric, newMetric]);
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
// -> 'speciesCount metric changed'
// -> [3, 4]
metrics.delListener(listenerId);
delListener
The delListener method removes a listener that was previously added to the Metrics object.
delListener(listenerId: string): Metrics| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Metrics | A reference to the |
Use the Id returned by the addMetricListener method. Note that the Metrics object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Metrics object, registers a listener, and then removes it.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const listenerId = metrics.addMetricListener(
'highestPrice',
(metrics, metricId, newMetric, oldMetric) => {
console.log('highestPrice metric changed');
},
);
store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
metrics.delListener(listenerId);
store.setCell('species', 'giraffe', 'price', 50);
// -> undefined
// The listener is not called.
Configuration methods
This is the collection of configuration methods within the Metrics interface. There are only two configuration methods, delMetricDefinition and setMetricDefinition.
delMetricDefinition
The delMetricDefinition method removes an existing Metric definition.
delMetricDefinition(metricId: string): MetricsExample
This example creates a Store, creates a Metrics object, defines a simple Metric, and then removes it.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetricIds());
// -> ['speciesCount']
metrics.delMetricDefinition('speciesCount');
console.log(metrics.getMetricIds());
// -> []
setMetricDefinition
The setMetricDefinition method lets you set the definition of a Metric.
setMetricDefinition(
metricId: string,
tableId: string,
aggregate?: MetricAggregate | "sum" | "avg" | "min" | "max",
getNumber?: string | (getCell: GetCell, rowId: string) => number,
aggregateAdd?: MetricAggregateAdd,
aggregateRemove?: MetricAggregateRemove,
aggregateReplace?: MetricAggregateReplace,
): Metrics| Type | Description | |
|---|---|---|
metricId | string | |
tableId | string | |
aggregate? | MetricAggregate | "sum" | "avg" | "min" | "max" | Either a string representing one of a set of common aggregation techniques ('sum', 'avg', 'min', or 'max'), or a function that aggregates numeric values from each |
getNumber? | string | (getCell: GetCell, rowId: string) => number | Either the |
aggregateAdd? | MetricAggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | MetricAggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | MetricAggregateReplace | A function that can be used to optimize a custom |
| returns | Metrics | A reference to the |
Every Metric definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
A Metric is an aggregation of numeric values produced from each Row within a single Table. Therefore the definition must specify the Table (by its Id) to be aggregated.
Without the third aggregate parameter, the Metric will simply be a count of the number of Row objects in the Table. But often you will specify a more interesting aggregate - such as the four predefined aggregates, 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of numbers.
The fourth getNumber parameter specifies which Cell in each Row contains the numerical values to be used in the aggregation. Alternatively, a custom function can be provided that produces your own numeric value from the local Row as a whole.
The final three parameters, aggregateAdd, aggregateRemove, aggregateReplace need only be provided when you are using your own custom aggregate function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.
Examples
This example creates a Store, creates a Metrics object, and defines a simple Metric to count the Row objects in the Table.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetric('speciesCount'));
// -> 3
This example creates a Store, creates a Metrics object, and defines a standard Metric to get the highest value of each price Cell in the Row objects in the Table.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
console.log(metrics.getMetric('highestPrice'));
// -> 5
This example creates a Store, creates a Metrics object, and defines a custom Metric to get the lowest value of each price Cell, greater than 2.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'lowestPriceOver2',
'species',
(numbers) => Math.min(...numbers.filter((number) => number > 2)),
'price',
);
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
This example also creates a Store, creates a Metrics object, and defines a custom Metric to get the lowest value of each price Cell, greater than 2. However, it also reduces algorithmic complexity with two shortcut functions.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'lowestPriceOver2',
'species',
(numbers) => Math.min(...numbers.filter((number) => number > 2)),
'price',
(metric, add) => (add > 2 ? Math.min(metric, add) : metric),
(metric, remove) => (remove == metric ? undefined : metric),
(metric, add, remove) =>
remove == metric
? undefined
: add > 2
? Math.min(metric, add)
: metric,
);
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
store.setRow('species', 'fish', {price: 3});
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 3
This example creates a Store, creates a Metrics object, and defines a custom Metric to get the average value of a discounted price.
const store = createStore().setTable('species', {
dog: {price: 5, discount: 0.3},
cat: {price: 4, discount: 0.2},
worm: {price: 1, discount: 0.2},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition(
'averageDiscountedPrice',
'species',
'avg',
(getCell) => getCell('price') * (1 - getCell('discount')),
);
console.log(metrics.getMetric('averageDiscountedPrice'));
// -> 2.5
Iterator methods
This is the collection of iterator methods within the Metrics interface. There is only one method, forEachMetric.
forEachMetric
The forEachMetric method takes a function that it will then call for each Metric in the Metrics object.
forEachMetric(metricCallback: MetricCallback): void| Type | Description | |
|---|---|---|
metricCallback | MetricCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over all the Metrics in a functional style. The metricCallback parameter is a MetricCallback function that will be called with the Id of each Metric and its value.
Example
This example iterates over each Metric in a Metrics object.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store)
.setMetricDefinition('highestPrice', 'species', 'max', 'price')
.setMetricDefinition('lowestPrice', 'species', 'min', 'price');
metrics.forEachMetric((metricId, metric) => {
console.log([metricId, metric]);
});
// -> ['highestPrice', 5]
// -> ['lowestPrice', 1]
Lifecycle methods
This is the collection of lifecycle methods within the Metrics interface. There is only one method, destroy.
destroy
The destroy method should be called when this Metrics object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Metrics object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(store.getListenerStats().row);
// -> 1
metrics.destroy();
console.log(store.getListenerStats().row);
// -> 0
Development methods
This is the collection of development methods within the Metrics interface. There is only one method, getListenerStats.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Metrics object, and is used for debugging purposes.
getListenerStats(): MetricsListenerStats| returns | MetricsListenerStats | A |
|---|
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Metrics object.
const store = createStore();
const metrics = createMetrics(store);
metrics.addMetricListener(null, () => console.log('Metric changed'));
console.log(metrics.getListenerStats());
// -> {metric: 1}
Functions
There is one function, createMetrics, within the metrics module.
createMetrics
The createMetrics function creates a Metrics object, and is the main entry point into the metrics module.
createMetrics(store: Store): MetricsA given Store can only have one Metrics object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Metrics object created by the first.
Examples
This example creates a Metrics object.
const store = createStore();
const metrics = createMetrics(store);
console.log(metrics.getMetricIds());
// -> []
This example creates a Metrics object, and calls the method a second time for the same Store to return the same object.
const store = createStore();
const metrics1 = createMetrics(store);
const metrics2 = createMetrics(store);
console.log(metrics1 === metrics2);
// -> true
Type Aliases
These are the type aliases within the metrics module.
Listener type aliases
This is the collection of listener type aliases within the metrics module. There are only two listener type aliases, MetricIdsListener and MetricListener.
MetricIdsListener
The MetricIdsListener type describes a function that is used to listen to Metric definitions being added or removed.
(metrics: Metrics): void| Type | Description | |
|---|---|---|
metrics | Metrics | A reference to the |
| returns | void | This has no return value. |
A MetricIdsListener is provided when using the addMetricIdsListener method. See that method for specific examples.
When called, a MetricIdsListener is given a reference to the Metrics object.
MetricListener
The MetricListener type describes a function that is used to listen to changes to a Metric.
(
metrics: Metrics,
metricId: Id,
newMetric: Metric | undefined,
oldMetric: Metric | undefined,
): void| Type | Description | |
|---|---|---|
metrics | Metrics | A reference to the |
metricId | Id | |
newMetric | Metric | undefined | The new value of the |
oldMetric | Metric | undefined | The old value of the |
| returns | void | This has no return value. |
A MetricListener is provided when using the addMetricListener method. See that method for specific examples.
When called, a MetricListener is given a reference to the Metrics object, the Id of the Metric that changed, and the new and old values of the Metric.
If this is the first time that a Metric has had a value (such as when a table has gained its first row), the old value will be undefined. If a Metric now no longer has a value, the new value will be undefined.
Aggregators type aliases
This is the collection of aggregators type aliases within the metrics module. There are 4 aggregators type aliases in total.
MetricAggregate
The MetricAggregate type describes a custom function that takes an array of numbers and returns an aggregate that is used as a Metric.
(
numbers: number[],
length: number,
): Metric| Type | Description | |
|---|---|---|
numbers | number[] | The array of numbers in the |
length | number | The length of the array of numbers in the |
| returns | Metric | The value of the |
There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising. See the setMetricDefinition method for more examples.
MetricAggregateAdd
The MetricAggregateAdd type describes a function that can be used to optimize a custom MetricAggregate by providing a shortcut for when a single value is added to the input values.
(
metric: Metric,
add: number,
length: number,
): Metric | undefined| Type | Description | |
|---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
length | number | The length of the array of numbers in the |
| returns | Metric | undefined | The new value of the |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.
If it is not possible to shortcut the aggregation based on just one value being added, return undefined and the Metric will be completely recalculated.
Where possible, if you are providing a custom MetricAggregate, seek an implementation of an MetricAggregateAdd function that can reduce the complexity cost of growing the input data set. See the setMetricDefinition method for more examples.
MetricAggregateRemove
The MetricAggregateRemove type describes a function that can be used to optimize a custom MetricAggregate by providing a shortcut for when a single value is removed from the input values.
(
metric: Metric,
remove: number,
length: number,
): Metric | undefined| Type | Description | |
|---|---|---|
metric | Metric | The current value of the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
| returns | Metric | undefined | The new value of the |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.
If it is not possible to shortcut the aggregation based on just one value being removed, return undefined and the Metric will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.
Where possible, if you are providing a custom MetricAggregate, seek an implementation of an MetricAggregateRemove function that can reduce the complexity cost of shrinking the input data set. See the setMetricDefinition method for more examples.
MetricAggregateReplace
The MetricAggregateReplace type describes a function that can be used to optimize a custom MetricAggregate by providing a shortcut for when a single value in the input values is replaced with another.
(
metric: Metric,
add: number,
remove: number,
length: number,
): Metric | undefined| Type | Description | |
|---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
| returns | Metric | undefined | The new value of the |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.
If it is not possible to shortcut the aggregation based on just one value changing, return undefined and the Metric will be completely recalculated.
Where possible, if you are providing a custom MetricAggregate, seek an implementation of an MetricAggregateReplace function that can reduce the complexity cost of changing the input data set in place. See the setMetricDefinition method for more examples.
Callback type aliases
This is the collection of callback type aliases within the metrics module. There is only one type alias, MetricCallback.
MetricCallback
The MetricCallback type describes a function that takes a Metric's Id and a callback to loop over each Row within it.
(
metricId: Id,
metric?: Metric,
): voidA MetricCallback is provided when using the forEachMetric method, so that you can do something based on every Metric in the Metrics object. See that method for specific examples.
Metric type aliases
This is the collection of metric type aliases within the metrics module. There is only one type alias, Metric.
Metric
The Metric type is simply an alias, but represents a number formed by aggregating multiple other numbers together.
numberDevelopment type aliases
This is the collection of development type aliases within the metrics module. There is only one type alias, MetricsListenerStats.
MetricsListenerStats
The MetricsListenerStats type describes the number of listeners registered with the Metrics object, and can be used for debugging purposes.
{metric?: number}| Type | Description | |
|---|---|---|
metric? | number | The number of |
A MetricsListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.
indexes
The indexes module of the TinyBase project provides the ability to create and track indexes of the data in Store objects.
The main entry point to this module is the createIndexes function, which returns a new Indexes object. From there, you can create new Index definitions, access the contents of those Indexes directly, and register listeners for when they change.
Interfaces
There is one interface, Indexes, within the indexes module.
Indexes
An Indexes object lets you look up all the Row objects in a Table that have a certain Cell value.
This is useful for creating filtered views of a Table, or simple search functionality.
Create an Indexes object easily with the createIndexes function. From there, you can add new Index definitions (with the setIndexDefinition method), query their contents (with the getSliceIds method and getSliceRowIds method), and add listeners for when they change (with the addSliceIdsListener method and addSliceRowIdsListener method).
This module defaults to indexing Row objects by one of their Cell values. However, far more complex indexes can be configured with a custom function.
Example
This example shows a very simple lifecycle of an Indexes object: from creation, to adding a definition, getting its contents, and then registering and removing a listener for it.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'bySpecies', // indexId
'pets', // tableId to index
'species', // cellId to index
);
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
const listenerId = indexes.addSliceIdsListener('bySpecies', () => {
console.log(indexes.getSliceIds('bySpecies'));
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> ['dog', 'cat', 'worm']
indexes.delListener(listenerId);
indexes.destroy();
See also
Metrics And Indexes guides
Rolling Dice demos
Country demo
Todo App demos
Word Frequencies demo
Getter methods
This is the collection of getter methods within the Indexes interface. There are 7 getter methods in total.
getStore
The getStore method returns a reference to the underlying Store that is backing this Indexes object.
getStore(): StoreExample
This example creates an Indexes object against a newly-created Store and then gets its reference in order to update its data.
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog']
getTableId
The getTableId method returns the Id of the underlying Table that is backing an Index.
getTableId(indexId: string): undefined | string| Type | Description | |
|---|---|---|
indexId | string | |
| returns | undefined | string |
If the Index Id is invalid, the method returns undefined.
Example
This example creates an Indexes object, a single Index definition, and then queries it (and a non-existent definition) to get the underlying Table Id.
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getTableId('bySpecies'));
// -> 'pets'
console.log(indexes.getTableId('byColor'));
// -> undefined
getSliceRowIds
The getSliceRowIds method gets the list of Row Ids in a given Slice, within a given Index.
getSliceRowIds(
indexId: string,
sliceId: string,
): Ids| Type | Description | |
|---|---|---|
indexId | string | |
sliceId | string | |
| returns | Ids |
If the identified Index or Slice do not exist (or if the definition references a Table that does not exist) then an empty array is returned.
Example
This example creates a Store, creates an Indexes object, and defines a simple Index. It then uses getSliceRowIds to see the Row Ids in the Slice (and also the Row Ids in Slices that do not exist).
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(indexes.getSliceRowIds('bySpecies', 'worm'));
// -> []
console.log(indexes.getSliceRowIds('byColor', 'brown'));
// -> []
getIndexIds
The getIndexIds method returns an array of the Index Ids registered with this Indexes object.
getIndexIds(): IdsExample
This example creates an Indexes object with two definitions, and then gets the Ids of the definitions.
const indexes = createIndexes(createStore())
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
console.log(indexes.getIndexIds());
// -> ['bySpecies', 'byColor']
getSliceIds
The getSliceIds method gets the list of Slice Ids in an Index.
getSliceIds(indexId: string): Ids| Type | Description | |
|---|---|---|
indexId | string | |
| returns | Ids |
If the identified Index does not exist (or if the definition references a Table that does not exist) then an empty array is returned.
Example
This example creates a Store, creates an Indexes object, and defines a simple Index. It then uses getSliceIds to see the available Slice Ids in the Index (and also the Slice Ids in an Index that has not been defined).
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceIds('byColor'));
// -> []
hasIndex
The hasIndex method returns a boolean indicating whether a given Index exists in the Indexes object.
hasIndex(indexId: string): boolean| Type | Description | |
|---|---|---|
indexId | string | |
| returns | boolean |
Example
This example shows two simple Index existence checks.
const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasIndex('bySpecies'));
// -> true
console.log(indexes.hasIndex('byColor'));
// -> false
hasSlice
The hasSlice method returns a boolean indicating whether a given Slice exists in the Indexes object.
hasSlice(
indexId: string,
sliceId: string,
): boolean| Type | Description | |
|---|---|---|
indexId | string | |
sliceId | string | |
| returns | boolean |
Example
This example shows two simple Index existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasSlice('bySpecies', 'dog'));
// -> true
console.log(indexes.hasSlice('bySpecies', 'worm'));
// -> false
Listener methods
This is the collection of listener methods within the Indexes interface. There are 4 listener methods in total.
addSliceRowIdsListener
The addSliceRowIdsListener method registers a listener function with the Indexes object that will be called whenever the Row Ids in a Slice change.
addSliceRowIdsListener(
indexId: IdOrNull,
sliceId: IdOrNull,
listener: SliceRowIdsListener,
): string| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
| returns | string | A unique |
You can either listen to a single Slice (by specifying the Index Id and Slice Id as the method's first two parameters), or changes to any Slice (by providing null wildcards).
Both, either, or neither of the indexId and sliceId parameters can be wildcarded with null. You can listen to a specific Slice in a specific Index, any Slice in a specific Index, a specific Slice in any Index, or any Slice in any Index.
The provided listener is a SliceRowIdsListener function, and will be called with a reference to the Indexes object, the Id of the Index, and the Id of the Slice that changed.
Examples
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to a specific Slice.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceRowIdsListener(
'bySpecies',
'dog',
(indexes, indexId, sliceId) => {
console.log('Row Ids for dog slice in bySpecies index changed');
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
indexes.delListener(listenerId);
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to any Slice.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
const listenerId = indexes.addSliceRowIdsListener(
null,
null,
(indexes, indexId, sliceId) => {
console.log(
`Row Ids for ${sliceId} slice in ${indexId} index changed`,
);
console.log(indexes.getSliceRowIds(indexId, sliceId));
},
);
store.setRow('pets', 'toto', {species: 'dog', color: 'brown'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
// -> 'Row Ids for brown slice in byColor index changed'
// -> ['fido', 'toto']
indexes.delListener(listenerId);
addIndexIdsListener
The addIndexIdsListener method registers a listener function with the Indexes object that will be called whenever an Index definition is added or removed.
addIndexIdsListener(listener: IndexIdsListener): string| Type | Description | |
|---|---|---|
listener | IndexIdsListener | The function that will be called whenever an |
| returns | string |
The provided listener is an IndexIdsListener function, and will be called with a reference to the Indexes object.
Example
This example creates a Store, an Indexes object, and then registers a listener that responds to the addition and the removal of an Index definition.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
const listenerId = indexes.addIndexIdsListener((indexes) => {
console.log(indexes.getIndexIds());
});
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
// -> ['bySpecies']
indexes.delIndexDefinition('bySpecies');
// -> []
indexes.delListener(listenerId);
Since
v4.1.0
addSliceIdsListener
The addSliceIdsListener method registers a listener function with the Indexes object that will be called whenever the Slice Ids in an Index change.
addSliceIdsListener(
indexId: IdOrNull,
listener: SliceIdsListener,
): string| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
| returns | string | A unique |
You can either listen to a single Index (by specifying the Index Id as the method's first parameter), or changes to any Index (by providing a null wildcard).
The provided listener is a SliceIdsListener function, and will be called with a reference to the Indexes object, and the Id of the Index that changed.
Examples
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to a specific Index.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceIdsListener(
'bySpecies',
(indexes, indexId) => {
console.log('Slice Ids for bySpecies index changed');
console.log(indexes.getSliceIds('bySpecies'));
},
);
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
indexes.delListener(listenerId);
This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to any Index.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
const listenerId = indexes.addSliceIdsListener(
null,
(indexes, indexId) => {
console.log(`Slice Ids for ${indexId} index changed`);
console.log(indexes.getSliceIds(indexId));
},
);
store.setRow('pets', 'lowly', {species: 'worm', color: 'pink'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
// -> 'Slice Ids for byColor index changed'
// -> ['brown', 'black', 'pink']
indexes.delListener(listenerId);
delListener
The delListener method removes a listener that was previously added to the Indexes object.
delListener(listenerId: string): Indexes| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Indexes | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Indexes object may re-use this Id for future listeners added to it.
Example
This example creates a Store, an Indexes object, registers a listener, and then removes it.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const listenerId = indexes.addSliceIdsListener(
'bySpecies',
(indexes, indexId) => {
console.log('Slice Ids for bySpecies index changed');
},
);
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
indexes.delListener(listenerId);
store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Configuration methods
This is the collection of configuration methods within the Indexes interface. There are only two configuration methods, delIndexDefinition and setIndexDefinition.
delIndexDefinition
The delIndexDefinition method removes an existing Index definition.
delIndexDefinition(indexId: string): IndexesExample
This example creates a Store, creates an Indexes object, defines a simple Index, and then removes it.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getIndexIds());
// -> ['bySpecies']
indexes.delIndexDefinition('bySpecies');
console.log(indexes.getIndexIds());
// -> []
setIndexDefinition
The setIndexDefinition method lets you set the definition of an Index.
setIndexDefinition(
indexId: string,
tableId: string,
getSliceIdOrIds?: string | (getCell: GetCell, rowId: string) => string | Ids,
getSortKey?: string | (getCell: GetCell, rowId: string) => SortKey,
sliceIdSorter?: (sliceId1: string, sliceId2: string) => number,
rowIdSorter?: (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number,
): Indexes| Type | Description | |
|---|---|---|
indexId | string | |
tableId | string | |
getSliceIdOrIds? | string | (getCell: GetCell, rowId: string) => string | Ids | Either the |
getSortKey? | string | (getCell: GetCell, rowId: string) => SortKey | Either the |
sliceIdSorter? | (sliceId1: string, sliceId2: string) => number | A function that takes two |
rowIdSorter? | (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number | A function that takes two |
| returns | Indexes | A reference to the |
Every Index definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
An Index is a keyed map of Slice objects, each of which is a list of Row Ids from a given Table. Therefore the definition must specify the Table (by its Id) to be indexed.
The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by this method. Those values are used as the key for each Slice in the overall Index object.
Without the third getSliceIdOrIds parameter, the Index will simply have a single Slice, keyed by an empty string. But more often you will specify a Cell value containing the Slice Id that the Row should belong to. Alternatively, a custom function can be provided that produces your own Slice Id from the local Row as a whole. Since v2.1, the custom function can return an array of Slice Ids, each of which the Row will then belong to.
The fourth getSortKey parameter specifies a Cell Id to get a value (or a function that processes a whole Row to get a value) that is used to sort the Row Ids within each Slice in the Index.
The fifth parameter, sliceIdSorter, lets you specify a way to sort the Slice Ids when you access the Index, which may be useful if you are trying to create an alphabetic Index of Row entries. If not specified, the order of the Slice Ids will match the order of Row insertion.
The final parameter, rowIdSorter, lets you specify a way to sort the Row Ids within each Slice, based on the getSortKey parameter. This may be useful if you are trying to keep Rows in a determined order relative to each other in the Index. If omitted, the Row Ids are sorted alphabetically, based on the getSortKey parameter.
The two 'sorter' parameters, sliceIdSorter and rowIdSorter, are functions that take two values and return a positive or negative number for when they are in the wrong or right order, respectively. This is exactly the same as the 'compareFunction' that is used in the standard JavaScript array sort method, with the addition that rowIdSorter also takes the Slice Id parameter, in case you want to sort Row Ids differently in each Slice. You can use the convenient defaultSorter function to default this to be alphanumeric.
Examples
This example creates a Store, creates an Indexes object, and defines a simple Index based on the values in the species Cell.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('byFirst', 'pets', (_, rowId) => rowId[0]);
console.log(indexes.getSliceIds('byFirst'));
// -> ['f', 'c']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['fido', 'felix']
This example creates a Store, creates an Indexes object, and defines an Index based on each of the letters present in the pets' names.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
rex: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('containsLetter', 'pets', (_, rowId) =>
rowId.split(''),
);
console.log(indexes.getSliceIds('containsLetter'));
// -> ['f', 'i', 'd', 'o', 'e', 'l', 'x', 'r']
console.log(indexes.getSliceRowIds('containsLetter', 'i'));
// -> ['fido', 'felix']
console.log(indexes.getSliceRowIds('containsLetter', 'x'));
// -> ['felix', 'rex']
This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names. The Slice Ids (and Row Ids within them) are alphabetically sorted.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'byFirst', // indexId
'pets', // tableId
(_, rowId) => rowId[0], // each Row's sliceId
(_, rowId) => rowId, // each Row's sort key
defaultSorter, // sort Slice Ids
defaultSorter, // sort Row Ids
);
console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Iterator methods
This is the collection of iterator methods within the Indexes interface. There are only two iterator methods, forEachIndex and forEachSlice.
forEachIndex
The forEachIndex method takes a function that it will then call for each Index in a specified Indexes object.
forEachIndex(indexCallback: IndexCallback): void| Type | Description | |
|---|---|---|
indexCallback | IndexCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the structure of the Indexes object in a functional style. The indexCallback parameter is a IndexCallback function that will be called with the Id of each Index, and with a function that can then be used to iterate over each Slice of the Index, should you wish.
Example
This example iterates over each Index in an Indexes object, and lists each Slice Id within them.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
.setIndexDefinition('bySpecies', 'pets', 'species')
.setIndexDefinition('byColor', 'pets', 'color');
indexes.forEachIndex((indexId, forEachSlice) => {
console.log(indexId);
forEachSlice((sliceId) => console.log(`- ${sliceId}`));
});
// -> 'bySpecies'
// -> '- dog'
// -> '- cat'
// -> 'byColor'
// -> '- brown'
// -> '- black'
forEachSlice
The forEachSlice method takes a function that it will then call for each Slice in a specified Index.
forEachSlice(
indexId: string,
sliceCallback: SliceCallback,
): void| Type | Description | |
|---|---|---|
indexId | string | |
sliceCallback | SliceCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the Slice structure of the Index in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id and value of each Row in the Slice.
Example
This example iterates over each Row in a Slice, and lists its Id.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.forEachSlice('bySpecies', (sliceId, forEachRow) => {
console.log(sliceId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dog'
// -> '- fido'
// -> '- cujo'
// -> 'cat'
// -> '- felix'
Lifecycle methods
This is the collection of lifecycle methods within the Indexes interface. There is only one method, destroy.
destroy
The destroy method should be called when this Indexes object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds an Indexes object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(store.getListenerStats().row);
// -> 1
indexes.destroy();
console.log(store.getListenerStats().row);
// -> 0
Development methods
This is the collection of development methods within the Indexes interface. There is only one method, getListenerStats.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Indexes object, and is used for debugging purposes.
getListenerStats(): IndexesListenerStats| returns | IndexesListenerStats | A |
|---|
The IndexesListenerStats object contains a breakdown of the different types of listener.
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of an Indexes object.
const store = createStore();
const indexes = createIndexes(store);
indexes.addSliceIdsListener(null, () => {
console.log('Slice Ids changed');
});
indexes.addSliceRowIdsListener(null, null, () => {
console.log('Slice Row Ids changed');
});
console.log(indexes.getListenerStats());
// -> {sliceIds: 1, sliceRowIds: 1}
Functions
There is one function, createIndexes, within the indexes module.
createIndexes
The createIndexes function creates an Indexes object, and is the main entry point into the indexes module.
createIndexes(store: Store): IndexesA given Store can only have one Indexes object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Indexes object created by the first.
Examples
This example creates an Indexes object.
const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []
This example creates an Indexes object, and calls the method a second time for the same Store to return the same object.
const store = createStore();
const indexes1 = createIndexes(store);
const indexes2 = createIndexes(store);
console.log(indexes1 === indexes2);
// -> true
Type Aliases
These are the type aliases within the indexes module.
Listener type aliases
This is the collection of listener type aliases within the indexes module. There are only three listener type aliases, SliceRowIdsListener, IndexIdsListener, and SliceIdsListener.
SliceRowIdsListener
The SliceRowIdsListener type describes a function that is used to listen to changes to the Row Ids in a Slice.
(
indexes: Indexes,
indexId: Id,
sliceId: Id,
): void| Type | Description | |
|---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
sliceId | Id | |
| returns | void | This has no return value. |
A SliceRowIdsListener is provided when using the addSliceRowIdsListener method. See that method for specific examples.
When called, a SliceRowIdsListener is given a reference to the Indexes object, the Id of the Index that changed, and the Id of the Slice whose Row Ids changed.
IndexIdsListener
The IndexIdsListener type describes a function that is used to listen to Index definitions being added or removed.
(indexes: Indexes): void| Type | Description | |
|---|---|---|
indexes | Indexes | A reference to the |
| returns | void | This has no return value. |
A IndexIdsListener is provided when using the addIndexIdsListener method. See that method for specific examples.
When called, an IndexIdsListener is given a reference to the Indexes object.
SliceIdsListener
The SliceIdsListener type describes a function that is used to listen to changes to the Slice Ids in an Index.
(
indexes: Indexes,
indexId: Id,
): void| Type | Description | |
|---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
| returns | void | This has no return value. |
A SliceIdsListener is provided when using the addSliceIdsListener method. See that method for specific examples.
When called, a SliceIdsListener is given a reference to the Indexes object, and the Id of the Index that changed.
Callback type aliases
This is the collection of callback type aliases within the indexes module. There are only two callback type aliases, IndexCallback and SliceCallback.
IndexCallback
The IndexCallback type describes a function that takes an Index's Id and a callback to loop over each Slice within it.
(
indexId: Id,
forEachSlice: (sliceCallback: SliceCallback) => void,
): void| Type | Description | |
|---|---|---|
indexId | Id | |
forEachSlice | (sliceCallback: SliceCallback) => void | |
| returns | void | This has no return value. |
A IndexCallback is provided when using the forEachIndex method, so that you can do something based on every Index in the Indexes object. See that method for specific examples.
SliceCallback
The SliceCallback type describes a function that takes a Slice's Id and a callback to loop over each Row within it.
(
sliceId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void| Type | Description | |
|---|---|---|
sliceId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A SliceCallback is provided when using the forEachSlice method, so that you can do something based on every Slice in an Index. See that method for specific examples.
Concept type aliases
This is the collection of concept type aliases within the indexes module. There are only two concept type aliases, Index and Slice.
Index
The Index type represents the concept of a map of Slice objects, keyed by Id.
{[sliceId: Id]: Slice}The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by the setIndexDefinition method. Those values are used as the key for each Slice in the overall Index object.
Note that the Index type is not actually used in the API, and you instead enumerate and access its structure with the getSliceIds method and getSliceRowIds method.
Slice
The Slice type represents the concept of a set of Row objects that comprise part of an Index.
IdsThe Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by the setIndexDefinition method.
Note that the Slice type is not actually used in the API, and you instead get Row Ids directly with the getSliceRowIds method.
Development type aliases
This is the collection of development type aliases within the indexes module. There is only one type alias, IndexesListenerStats.
IndexesListenerStats
The IndexesListenerStats type describes the number of listeners registered with the Indexes object, and can be used for debugging purposes.
{
sliceIds?: number;
sliceRowIds?: number;
}| Type | Description | |
|---|---|---|
sliceIds? | number | The number of SlideIdsListener functions registered with the |
sliceRowIds? | number | The number of |
A IndexesListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.
relationships
The relationships module of the TinyBase project provides the ability to create and track relationships between the data in Store objects.
The main entry point to this module is the createRelationships function, which returns a new Relationships object. From there, you can create new Relationship definitions, access the associations within those Relationships directly, and register listeners for when they change.
Interfaces
There is one interface, Relationships, within the relationships module.
Relationships
A Relationships object lets you associate a Row in a one Table with the Id of a Row in another Table.
This is useful for creating parent-child relationships between the data in different Table objects, but it can also be used to model a linked list of Row objects in the same Table.
Create a Relationships object easily with the createRelationships function. From there, you can add new Relationship definitions (with the setRelationshipDefinition method), query their contents (with the getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds method), and add listeners for when they change (with the addRemoteRowIdListener method, the addLocalRowIdsListener method, and the addLinkedRowIdsListener method).
This module defaults to creating relationships between Row objects by using one of their Cell values. However, far more complex relationships can be configured with a custom function.
Example
This example shows a very simple lifecycle of a Relationships object: from creation, to adding definitions (both local/remote table and linked list), getting their contents, and then registering and removing listeners for them.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
// A local/remote table relationship:
relationships.setRelationshipDefinition(
'petSpecies', // relationshipId
'pets', // localTableId to link from
'species', // remoteTableId to link to
'species', // cellId containing remote key
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
// A linked list relationship:
relationships.setRelationshipDefinition(
'petSequence', // relationshipId
'pets', // localTableId to link from
'pets', // the same remoteTableId to link within
'next', // cellId containing link key
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
const listenerId1 = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
() => {
console.log('petSpecies relationship (to dog) changed');
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
},
);
const listenerId2 = relationships.addLinkedRowIdsListener(
'petSequence',
'fido',
() => {
console.log('petSequence linked list (from fido) changed');
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']
relationships.delListener(listenerId1);
relationships.delListener(listenerId2);
relationships.destroy();
See also
Relationships And Checkpoints guides
Drawing demo
Getter methods
This is the collection of getter methods within the Relationships interface. There are 8 getter methods in total.
getStore
The getStore method returns a reference to the underlying Store that is backing this Relationships object.
getStore(): StoreExample
This example creates a Relationships object against a newly-created Store and then gets its reference in order to update its data.
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
relationships.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
getLocalTableId
The getLocalTableId method returns the Id of the underlying local Table that is used in the Relationship.
getLocalTableId(relationshipId: string): undefined | string| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | undefined | string | The |
If the Relationship Id is invalid, the method returns undefined.
Example
This example creates a Relationship object, a single Relationship definition, and then queries it (and a non-existent definition) to get the underlying local Table Id.
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getLocalTableId('petSpecies'));
// -> 'pets'
console.log(relationships.getLocalTableId('petColor'));
// -> undefined
getRemoteTableId
The getRemoteTableId method returns the Id of the underlying remote Table that is used in the Relationship.
getRemoteTableId(relationshipId: string): undefined | string| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | undefined | string | The |
If the Relationship Id is invalid, the method returns undefined.
Example
This example creates a Relationship object, a single Relationship definition, and then queries it (and a non-existent definition) to get the underlying remote Table Id.
const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRemoteTableId('petSpecies'));
// -> 'species'
console.log(relationships.getRemoteTableId('petColor'));
// -> undefined
getLinkedRowIds
The getLinkedRowIds method gets the linked Row Ids for a given Row in a linked list Relationship.
getLinkedRowIds(
relationshipId: string,
firstRowId: string,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | The |
| returns | Ids | The linked |
A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.
If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then an array containing just the first Row Id is returned.
Example
This example creates a Store, creates a Relationships object, and defines a simple linked list Relationship. It then uses getLinkedRowIds to see the linked Row Ids in the Relationship (and also the linked Row Ids for a Row that does not exist, and for a Relationship that has not been defined).
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'felix'));
// -> ['felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'toto'));
// -> ['toto']
console.log(relationships.getLinkedRowIds('petFriendships', 'fido'));
// -> ['fido']
getLocalRowIds
The getLocalRowIds method gets the local Row Ids for a given remote Row in a Relationship.
getLocalRowIds(
relationshipId: string,
remoteRowId: string,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
| returns | Ids | The local |
If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then an empty array is returned.
Example
This example creates a Store, creates a Relationships object, and defines a simple Relationship. It then uses getLocalRowIds to see the local Row Ids in the Relationship (and also the local Row Ids for a remote Row that does not exist, and for a Relationship that has not been defined).
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(relationships.getLocalRowIds('petSpecies', 'worm'));
// -> []
console.log(relationships.getLocalRowIds('petColor', 'brown'));
// -> []
getRemoteRowId
The getRemoteRowId method gets the remote Row Id for a given local Row in a Relationship.
getRemoteRowId(
relationshipId: string,
localRowId: string,
): undefined | string| Type | Description | |
|---|---|---|
relationshipId | string | The |
localRowId | string | The |
| returns | undefined | string | The remote |
If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then undefined is returned.
Example
This example creates a Store, creates a Relationships object, and defines a simple Relationship. It then uses getRemoteRowId to see the remote Row Id in the Relationship (and also the remote Row Ids for a local Row that does not exist, and for a Relationship that has not been defined).
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getRemoteRowId('petSpecies', 'toto'));
// -> undefined
console.log(relationships.getRemoteRowId('petColor', 'fido'));
// -> undefined
getRelationshipIds
The getRelationshipIds method returns an array of the Relationship Ids registered with this Relationships object.
getRelationshipIds(): IdsExample
This example creates a Relationships object with two definitions, and then gets the Ids of the definitions.
const relationships = createRelationships(createStore())
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
console.log(relationships.getRelationshipIds());
// -> ['petSpecies', 'petSequence']
hasRelationship
The hasRelationship method returns a boolean indicating whether a given Relationship exists in the Relationships object.
hasRelationship(indexId: string): boolean| Type | Description | |
|---|---|---|
indexId | string | |
| returns | boolean | Whether a |
Example
This example shows two simple Relationship existence checks.
const relationships = createRelationships(
createStore(),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
console.log(relationships.hasRelationship('petSpecies'));
// -> true
console.log(relationships.hasRelationship('petColor'));
// -> false
Listener methods
This is the collection of listener methods within the Relationships interface. There are 5 listener methods in total.
addLinkedRowIdsListener
The addLinkedRowIdsListener method registers a listener function with the Relationships object that will be called whenever the linked Row Ids in a linked list Relationship change.
addLinkedRowIdsListener(
relationshipId: string,
firstRowId: string,
listener: LinkedRowIdsListener,
): string| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
| returns | string | A unique |
A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.
You listen to changes to a linked list starting from a single first Row by specifying the Relationship Id and local Row Id as the method's first two parameters.
Unlike other listener registration methods, you cannot provide null wildcards for the first two parameters of the addLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store.
The provided listener is a LinkedRowIdsListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the first Row that had its linked list change.
Example
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific first Row's linked Row objects.
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const listenerId = relationships.addLinkedRowIdsListener(
'petSequence',
'fido',
(relationships, relationshipId, firstRowId) => {
console.log('petSequence linked list (from fido) changed');
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']
relationships.delListener(listenerId);
addLocalRowIdsListener
The addLocalRowIdsListener method registers a listener function with the Relationships object that will be called whenever the local Row Ids in a Relationship change.
addLocalRowIdsListener(
relationshipId: IdOrNull,
remoteRowId: IdOrNull,
listener: LocalRowIdsListener,
): string| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
| returns | string | A unique |
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and remoteRowId parameters can be wildcarded with null. You can listen to a specific remote Row in a specific Relationship, any remote Row in a specific Relationship, a specific remote Row in any Relationship, or any remote Row in any Relationship.
The provided listener is a LocalRowIdsListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the remote Row that had its local Row objects change.
Examples
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific remote Row's local Row objects.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
(relationships, relationshipId, remoteRowId) => {
console.log('petSpecies relationship (to dog) changed');
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']
relationships.delListener(listenerId);
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to any remote Row's local Row objects.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
toto: {species: 'dog', color: 'grey'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
})
.setTable('color', {
brown: {discount: 0.1},
black: {discount: 0},
grey: {discount: 0.2},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petColor', 'pets', 'color', 'color');
const listenerId = relationships.addLocalRowIdsListener(
null,
null,
(relationships, relationshipId, remoteRowId) => {
console.log(
`${relationshipId} relationship (to ${remoteRowId}) changed`,
);
console.log(relationships.getLocalRowIds(relationshipId, remoteRowId));
},
);
store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'toto']
// -> 'petSpecies relationship (to wolf) changed'
// -> ['cujo']
// -> 'petColor relationship (to brown) changed'
// -> ['fido']
// -> 'petColor relationship (to grey) changed'
// -> ['toto', 'cujo']
relationships.delListener(listenerId);
addRemoteRowIdListener
The addRemoteRowIdListener method registers a listener function with the Relationships object that will be called whenever a remote Row Id in a Relationship changes.
addRemoteRowIdListener(
relationshipId: IdOrNull,
localRowId: IdOrNull,
listener: RemoteRowIdListener,
): string| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
| returns | string | A unique |
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and localRowId parameters can be wildcarded with null. You can listen to a specific local Row in a specific Relationship, any local Row in a specific Relationship, a specific local Row in any Relationship, or any local Row in any Relationship.
The provided listener is a RemoteRowIdListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the local Row that had its remote Row change.
Examples
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific local Row's remote Row.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addRemoteRowIdListener(
'petSpecies',
'cujo',
(relationships, relationshipId, localRowId) => {
console.log('petSpecies relationship (from cujo) changed');
console.log(relationships.getRemoteRowId('petSpecies', 'cujo'));
},
);
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
relationships.delListener(listenerId);
This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to any local Row's remote Row. It also illustrates how you can use the getStore method and the getRemoteRowId method to resolve the remote Row as a whole.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
})
.setTable('color', {
brown: {discount: 0.1},
black: {discount: 0},
grey: {discount: 0.2},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petColor', 'pets', 'color', 'color');
const listenerId = relationships.addRemoteRowIdListener(
null,
null,
(relationships, relationshipId, localRowId) => {
console.log(
`${relationshipId} relationship (from ${localRowId}) changed`,
);
console.log(relationships.getRemoteRowId(relationshipId, localRowId));
console.log(
relationships
.getStore()
.getRow(
relationships.getRemoteTableId(relationshipId),
relationships.getRemoteRowId(relationshipId, localRowId),
),
);
},
);
store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
// -> {price: 10}
// -> 'petColor relationship (from cujo) changed'
// -> 'grey'
// -> {discount: 0.2}
relationships.delListener(listenerId);
addRelationshipIdsListener
The addRelationshipIdsListener method registers a listener function with the Relationships object that will be called whenever a Relationship definition is added or removed.
addRelationshipIdsListener(listener: RelationshipIdsListener): string| Type | Description | |
|---|---|---|
listener | RelationshipIdsListener | The function that will be called whenever a |
| returns | string |
The provided listener is a RelationshipIdsListener function, and will be called with a reference to the Relationships object.
Example
This example creates a Store, a Relationships object, and then registers a listener that responds to the addition and the removal of a Relationship definition.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
const listenerId = relationships.addRelationshipIdsListener(
(relationships) => {
console.log(relationships.getRelationshipIds());
},
);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
// -> ['petSpecies']
relationships.delRelationshipDefinition('petSpecies');
// -> []
relationships.delListener(listenerId);
Since
v4.1.0
delListener
The delListener method removes a listener that was previously added to the Relationships object.
delListener(listenerId: string): Relationships| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Relationships | A reference to the |
Use the Id returned by whichever method was used to add the listener. Note that the Relationships object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Relationships object, registers a listener, and then removes it.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const listenerId = relationships.addLocalRowIdsListener(
'petSpecies',
'dog',
(relationships, relationshipId, remoteRowId) => {
console.log('petSpecies relationship (to dog) changed');
},
);
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
relationships.delListener(listenerId);
store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Configuration methods
This is the collection of configuration methods within the Relationships interface. There are only two configuration methods, delRelationshipDefinition and setRelationshipDefinition.
delRelationshipDefinition
The delRelationshipDefinition method removes an existing Relationship definition.
delRelationshipDefinition(relationshipId: string): Relationships| Type | Description | |
|---|---|---|
relationshipId | string | The |
| returns | Relationships | A reference to the |
Example
This example creates a Store, creates a Relationships object, defines a simple Relationship, and then removes it.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(relationships.getRelationshipIds());
// -> ['petSpecies']
relationships.delRelationshipDefinition('petSpecies');
console.log(relationships.getRelationshipIds());
// -> []
setRelationshipDefinition
The setRelationshipDefinition method lets you set the definition of a Relationship.
setRelationshipDefinition(
relationshipId: string,
localTableId: string,
remoteTableId: string,
getRemoteRowId: string | (getCell: GetCell, localRowId: string) => string,
): Relationships| Type | Description | |
|---|---|---|
relationshipId | string | The |
localTableId | string | The |
remoteTableId | string | The |
getRemoteRowId | string | (getCell: GetCell, localRowId: string) => string | Either the |
| returns | Relationships | A reference to the |
Every Relationship definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
An Relationship is based on connections between Row objects, often in two different Table objects. Therefore the definition requires the localTableId parameter to specify the 'local' Table to create the Relationship from, and the remoteTableId parameter to specify the 'remote' Table to create Relationship to.
A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.
A local Row is related to a remote Row by specifying which of its (local) Cell values contains the (remote) Row Id, using the getRemoteRowId parameter. Alternatively, a custom function can be provided that produces your own remote Row Id from the local Row as a whole.
Examples
This example creates a Store, creates a Relationships object, and defines a simple Relationship based on the values in the species Cell of the pets Table that relates a Row to another in the species Table.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies', // relationshipId
'pets', // localTableId to link from
'species', // remoteTableId to link to
'species', // cellId containing remote key
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
This example creates a Store, creates a Relationships object, and defines a linked list Relationship based on the values in the next Cell of the pets Table that relates a Row to another in the same Table.
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence', // relationshipId
'pets', // localTableId to link from
'pets', // the same remoteTableId to link within
'next', // cellId containing link key
);
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
Iterator methods
This is the collection of iterator methods within the Relationships interface. There is only one method, forEachRelationship.
forEachRelationship
The forEachRelationship method takes a function that it will then call for each Relationship in a specified Relationships object.
forEachRelationship(relationshipCallback: RelationshipCallback): void| Type | Description | |
|---|---|---|
relationshipCallback | RelationshipCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over the structure of the Relationships object in a functional style. The relationshipCallback parameter is a RelationshipCallback function that will be called with the Id of each Relationship, and with a function that can then be used to iterate over each local Row involved in the Relationship.
Example
This example iterates over each Relationship in a Relationships object, and lists each Row Id within them.
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store)
.setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
.setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
relationships.forEachRelationship((relationshipId, forEachRow) => {
console.log(relationshipId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'petSpecies'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
// -> 'petSequence'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
Lifecycle methods
This is the collection of lifecycle methods within the Relationships interface. There is only one method, destroy.
destroy
The destroy method should be called when this Relationships object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Relationships object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {
wolf: {price: 10},
dog: {price: 5},
cat: {price: 4},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
console.log(store.getListenerStats().row);
// -> 1
relationships.destroy();
console.log(store.getListenerStats().row);
// -> 0
Development methods
This is the collection of development methods within the Relationships interface. There is only one method, getListenerStats.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Relationships object, and is used for debugging purposes.
getListenerStats(): RelationshipsListenerStats| returns | RelationshipsListenerStats | A |
|---|
The RelationshipsListenerStats object contains a breakdown of the different types of listener.
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Relationships object.
const store = createStore();
const relationships = createRelationships(store);
relationships.addRemoteRowIdListener(null, null, () => {
console.log('Remote Row Id changed');
});
relationships.addLocalRowIdsListener(null, null, () => {
console.log('Local Row Id changed');
});
const listenerStats = relationships.getListenerStats();
console.log(listenerStats.remoteRowId);
// -> 1
console.log(listenerStats.localRowIds);
// -> 1
Functions
There is one function, createRelationships, within the relationships module.
createRelationships
The createRelationships function creates a Relationships object, and is the main entry point into the relationships module.
createRelationships(store: Store): Relationships| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Relationships | A reference to the new |
A given Store can only have one Relationships object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Relationships object created by the first.
Examples
This example creates a Relationships object.
const store = createStore();
const relationships = createRelationships(store);
console.log(relationships.getRelationshipIds());
// -> []
This example creates a Relationships object, and calls the method a second time for the same Store to return the same object.
const store = createStore();
const relationships1 = createRelationships(store);
const relationships2 = createRelationships(store);
console.log(relationships1 === relationships2);
// -> true
Type Aliases
These are the type aliases within the relationships module.
Listener type aliases
This is the collection of listener type aliases within the relationships module. There are 4 listener type aliases in total.
LinkedRowIdsListener
The LinkedRowIdsListener type describes a function that is used to listen to changes to the local Row Id ends of a Relationship.
(
relationships: Relationships,
relationshipId: Id,
firstRowId: Id,
): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
firstRowId | Id | The |
| returns | void | This has no return value. |
A LinkedRowIdsListener is provided when using the addLinkedRowIdsListener method. See that method for specific examples.
When called, a LinkedRowIdsListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the first Row of the the linked list whose members changed.
LocalRowIdsListener
The LocalRowIdsListener type describes a function that is used to listen to changes to the local Row Id ends of a Relationship.
(
relationships: Relationships,
relationshipId: Id,
remoteRowId: Id,
): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
remoteRowId | Id | |
| returns | void | This has no return value. |
A LocalRowIdsListener is provided when using the addLocalRowIdsListener method. See that method for specific examples.
When called, a LocalRowIdsListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the remote Row whose local Row Ids changed.
RemoteRowIdListener
The RemoteRowIdListener type describes a function that is used to listen to changes to the remote Row Id end of a Relationship.
(
relationships: Relationships,
relationshipId: Id,
localRowId: Id,
): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
localRowId | Id | |
| returns | void | This has no return value. |
A RemoteRowIdListener is provided when using the addRemoteRowIdListener method. See that method for specific examples.
When called, a RemoteRowIdListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the local Row whose remote Row Id changed.
RelationshipIdsListener
The RelationshipIdsListener type describes a function that is used to listen to Relationship definitions being added or removed.
(relationships: Relationships): void| Type | Description | |
|---|---|---|
relationships | Relationships | A reference to the |
| returns | void | This has no return value. |
A RelationshipIdsListener is provided when using the addRelationshipIdsListener method. See that method for specific examples.
When called, a RelationshipIdsListener is given a reference to the Relationships object.
Callback type aliases
This is the collection of callback type aliases within the relationships module. There is only one type alias, RelationshipCallback.
RelationshipCallback
The RelationshipCallback type describes a function that takes a Relationship's Id and a callback to loop over each local Row within it.
(
relationshipId: Id,
forEachRow: (rowCallback: RowCallback) => void,
): void| Type | Description | |
|---|---|---|
relationshipId | Id | The |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the local |
| returns | void | This has no return value. |
A RelationshipCallback is provided when using the forEachRelationship method, so that you can do something based on every Relationship in the Relationships object. See that method for specific examples.
Concept type aliases
This is the collection of concept type aliases within the relationships module. There is only one type alias, Relationship.
Relationship
The Relationship type represents the concept of a map that connects one Row object to another, often in another Table.
{
remoteRowId: {[localRowId: Id]: Id};
localRowIds: {[remoteRowId: Id]: Ids};
linkedRowIds: {[firstRowId: Id]: Ids};
}| Type | Description | |
|---|---|---|
remoteRowId | {[localRowId: Id]: Id} | |
localRowIds | {[remoteRowId: Id]: Ids} | |
linkedRowIds | {[firstRowId: Id]: Ids} |
The Relationship has a one-to-many nature. One local Row Id is linked to one remote Row Id (in the remote Table), as described by the setRelationshipDefinition method - and one remote Row Id may map back to multiple local Row Ids (in the local Table).
A Relationship where the local Table is the same as the remote Table can be used to model a 'linked list', where Row A references Row B, Row B references Row C, and so on.
Note that the Relationship type is not actually used in the API, and you instead enumerate and access its structure with the getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds method.
Development type aliases
This is the collection of development type aliases within the relationships module. There is only one type alias, RelationshipsListenerStats.
RelationshipsListenerStats
The RelationshipsListenerStats type describes the number of listeners registered with the Relationships object, and can be used for debugging purposes.
{
remoteRowId?: number;
localRowIds?: number;
linkedRowIds?: number;
}| Type | Description | |
|---|---|---|
remoteRowId? | number | The number of |
localRowIds? | number | The number of |
linkedRowIds? | number | The number of LinkedRowId functions registered with the |
A RelationshipsListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.
queries
The queries module of the TinyBase project provides the ability to create and track queries of the data in Store objects.
The main entry point to using the queries module is the createQueries function, which returns a new Queries object. That object in turn has methods that let you create new query definitions, access their results directly, and register listeners for when those results change.
Since
v2.0.0
Interfaces
There is one interface, Queries, within the queries module.
Queries
A Queries object lets you create and track queries of the data in Store objects.
This is useful for creating a reactive view of data that is stored in physical tables: selecting columns, joining tables together, filtering rows, aggregating data, sorting it, and so on.
This provides a generalized query concept for Store data. If you just want to create and track metrics, indexes, or relationships between rows, you may prefer to use the dedicated Metrics, Indexes, and Relationships objects, which have simpler APIs.
Create a Queries object easily with the createQueries function. From there, you can add new query definitions (with the setQueryDefinition method), query the results (with the getResultTable method, the getResultRow method, the getResultCell method, and so on), and add listeners for when they change (with the addResultTableListener method, the addResultRowListener method, the addResultCellListener method, and so on).
Example
This example shows a very simple lifecycle of a Queries object: from creation, to adding definitions, getting their contents, and then registering and removing listeners for them.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown', ownerId: '1'},
felix: {species: 'cat', color: 'black', ownerId: '2'},
cujo: {species: 'dog', color: 'black', ownerId: '3'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
// A filtered table query:
queries.setQueryDefinition('blackPets', 'pets', ({select, where}) => {
select('species');
where('color', 'black');
});
console.log(queries.getResultTable('blackPets'));
// -> {felix: {species: 'cat'}, cujo: {species: 'dog'}}
// A joined table query:
queries.setQueryDefinition('petOwners', 'pets', ({select, join}) => {
select('owners', 'name').as('owner');
join('owners', 'ownerId');
});
console.log(queries.getResultTable('petOwners'));
// -> {fido: {owner: 'Alice'}, felix: {owner: 'Bob'}, cujo: {owner: 'Carol'}}
// A grouped query:
queries.setQueryDefinition(
'colorPrice',
'pets',
({select, join, group}) => {
select('color');
select('species', 'price');
join('species', 'species');
group('price', 'avg');
},
);
console.log(queries.getResultTable('colorPrice'));
// -> {"1": {color: 'black', price: 4.5}, "0": {color: 'brown', price: 5}}
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
// -> ["0", "1"]
const listenerId = queries.addResultTableListener('colorPrice', () => {
console.log('Average prices per color changed');
console.log(queries.getResultTable('colorPrice'));
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
});
store.setRow('pets', 'lowly', {species: 'worm', color: 'brown'});
// -> 'Average prices per color changed'
// -> {"0": {color: 'brown', price: 3}, "1": {color: 'black', price: 4.5}}
// -> ["1", "0"]
queries.delListener(listenerId);
queries.destroy();
See also
Making Queries guides
Car Analysis demo
Movie Database demo
Since
v2.0.0
Getter methods
This is the collection of getter methods within the Queries interface. There are 4 getter methods in total.
getStore
The getStore method returns a reference to the underlying Store that is backing this Queries object.
getStore(): StoreExample
This example creates a Queries object against a newly-created Store and then gets its reference in order to update its data.
const queries = createQueries(createStore());
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
queries
.getStore()
.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}}
Since
v2.0.0
getTableId
The getTableId method returns the Id of the underlying Table that is backing a query.
getTableId(queryId: string): undefined | string| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | undefined | string |
If the query Id is invalid, the method returns undefined.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the underlying Table Id.
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getTableId('dogColors'));
// -> 'pets'
console.log(queries.getTableId('catColors'));
// -> undefined
Since
v2.0.0
getQueryIds
The getQueryIds method returns an array of the query Ids registered with this Queries object.
getQueryIds(): IdsExample
This example creates a Queries object with two definitions, and then gets the Ids of the definitions.
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
console.log(queries.getQueryIds());
// -> ['dogColors', 'catColors']
Since
v2.0.0
hasQuery
The hasQuery method returns a boolean indicating whether a given query exists in the Queries object.
hasQuery(queryId: string): boolean| Type | Description | |
|---|---|---|
queryId | string | |
| returns | boolean | Whether a query with that |
Example
This example shows two simple query existence checks.
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasQuery('dogColors'));
// -> true
console.log(queries.hasQuery('catColors'));
// -> false
Since
v2.0.0
Result methods
This is the collection of result methods within the Queries interface. There are 11 result methods in total.
getResultTable
The getResultTable method returns an object containing the entire data of the ResultTable of the given query.
getResultTable(queryId: string): ResultTable| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | ResultTable | An object containing the entire data of the |
This has the same behavior as a Store's getTable method. For example, if the query Id is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
console.log(queries.getResultTable('catColors'));
// -> {}
Since
v2.0.0
getResultTableCellIds
The getResultTableCellIds method returns the Ids of every ResultCell used across the ResultTable of the given query.
getResultTableCellIds(queryId: string): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | Ids | An array of the |
This has the same behavior as a Store's getTableCellIds method. For example, if the query Id is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultCell Ids.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
console.log(queries.getResultTableCellIds('dogColors'));
// -> ['color', 'legs']
console.log(queries.getResultTableCellIds('catColors'));
// -> []
Since
v4.1.0
hasResultTable
The hasResultTable method returns a boolean indicating whether a given ResultTable exists.
hasResultTable(queryId: string): boolean| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | boolean | Whether a |
Example
This example shows two simple ResultTable existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultTable('dogColors'));
// -> true
console.log(queries.hasResultTable('catColors'));
// -> false
Since
v2.0.0
getResultRowIds
The getResultRowIds method returns the Ids of every ResultRow in the ResultTable of the given query.
getResultRowIds(queryId: string): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | Ids | An array of the |
This has the same behavior as a Store's getRowIds method. For example, if the query Id is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow Ids.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowIds('dogColors'));
// -> ['fido', 'cujo']
console.log(queries.getResultRowIds('catColors'));
// -> []
Since
v2.0.0
getResultSortedRowIds
The getResultSortedRowIds method returns the Ids of every ResultRow in the ResultTable of the given query, sorted according to the values in a specified ResultCell.
getResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
| returns | Ids | An array of the sorted |
This has the same behavior as a Store's getSortedRowIds method. For example, if the query Id is invalid, the method returns an empty array. Similarly, the sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available ResultRow Ids if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the ResultTable is large. For a performant approach to tracking the sorted ResultRow Ids when they change, use the addResultSortedRowIdsListener method.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow Ids.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', 'color'));
// -> ['cujo', 'fido']
console.log(queries.getResultSortedRowIds('catColors', 'color'));
// -> []
Since
v2.0.0
getResultRow
The getResultRow method returns an object containing the entire data of a single ResultRow in the ResultTable of the given query.
getResultRow(
queryId: string,
rowId: string,
): ResultRow| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
| returns | ResultRow | An object containing the entire data of the |
This has the same behavior as a Store's getRow method. For example, if the query or ResultRow Id is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent ResultRow Id) to get the ResultRow.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRow('dogColors', 'fido'));
// -> {color: 'brown'}
console.log(queries.getResultRow('dogColors', 'felix'));
// -> {}
Since
v2.0.0
getResultRowCount
The getResultRowCount method returns the count of the ResultRow objects in the ResultTable of the given query.
getResultRowCount(queryId: string): number| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | number | The number of |
This has the same behavior as a Store's getRowCount method. For example, if the query Id is invalid, the method returns zero.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the ResultRow count.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowCount('dogColors'));
// -> 2
console.log(queries.getResultRowCount('catColors'));
// -> 0
Since
v4.1.0
hasResultRow
The hasResultRow method returns a boolean indicating whether a given ResultRow exists.
hasResultRow(
queryId: string,
rowId: string,
): boolean| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | |
| returns | boolean |
Example
This example shows two simple ResultRow existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultRow('dogColors', 'fido'));
// -> true
console.log(queries.hasResultRow('dogColors', 'felix'));
// -> false
Since
v2.0.0
getResultCellIds
The getResultCellIds method returns the Ids of every ResultCell in a given ResultRow, in the ResultTable of the given query.
getResultCellIds(
queryId: string,
rowId: string,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
| returns | Ids | An array of the |
This has the same behavior as a Store's getCellIds method. For example, if the query Id or ResultRow Id is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent ResultRow Id) to get the ResultCell Ids.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCellIds('dogColors', 'fido'));
// -> ['color']
console.log(queries.getResultCellIds('dogColors', 'felix'));
// -> []
Since
v2.0.0
getResultCell
The getResultCell method returns the value of a single ResultCell in a given ResultRow, in the ResultTable of the given query.
getResultCell(
queryId: string,
rowId: string,
cellId: string,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
cellId | string | The |
| returns | ResultCellOrUndefined | The value of the |
This has the same behavior as a Store's getCell method. For example, if the query, or ResultRow, or ResultCell Id is invalid, the method returns undefined.
Example
This example creates a Queries object, a single query definition, and then calls this method on it (as well as a non-existent ResultCell Id) to get the ResultCell.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCell('dogColors', 'fido', 'color'));
// -> 'brown'
console.log(queries.getResultCell('dogColors', 'fido', 'species'));
// -> undefined
Since
v2.0.0
hasResultCell
The hasResultCell method returns a boolean indicating whether a given ResultCell exists.
hasResultCell(
queryId: string,
rowId: string,
cellId: string,
): boolean| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | |
cellId | string | The |
| returns | boolean | Whether a |
Example
This example shows two simple ResultRow existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultCell('dogColors', 'fido', 'color'));
// -> true
console.log(queries.hasResultCell('dogColors', 'fido', 'species'));
// -> false
Since
v2.0.0
Listener methods
This is the collection of listener methods within the Queries interface. There are 10 listener methods in total.
addResultTableCellIdsListener
The addResultTableCellIdsListener method registers a listener function with the Queries object that will be called whenever the Cell Ids that appear anywhere in a ResultTable change.
addResultTableCellIdsListener(
queryId: IdOrNull,
listener: ResultTableCellIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableCellIdsListener | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a ResultTableCellIdsListener function, and will be called with a reference to the Queries object and the Id of the ResultTable that changed (which is also the query Id).
By default, such a listener is only called when a Cell Id is added to, or removed from, the ResultTable. To listen to all changes in the ResultTable, use the addResultTableListener method.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to any change to the Cell Ids of a specific ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColorsAndLegs',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableCellIdsListener(
'dogColorsAndLegs',
(queries, tableId) => {
console.log(`Cell Ids for dogColorsAndLegs result table changed`);
console.log(queries.getResultTableCellIds('dogColorsAndLegs'));
},
);
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Cell Ids for dogColorsAndLegs result table changed'
// -> ['color', 'legs']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultCell Ids of any ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
})
.setQueryDefinition('catColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'cat');
});
const listenerId = queries.addResultTableCellIdsListener(
null,
(queries, tableId) => {
console.log(`Cell Ids for ${tableId} result table changed`);
},
);
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Cell Ids for dogColorsAndLegs result table changed'
store.delCell('pets', 'felix', 'legs');
// -> 'Cell Ids for catColorsAndLegs result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultTableListener
The addResultTableListener method registers a listener function with the Queries object that will be called whenever data in a ResultTable changes.
addResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching |
| returns | string | A unique |
The provided listener is a ResultTableListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), and a GetResultCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to any changes to a specific ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableListener(
'dogColors',
(queries, tableId, getCellChange) => {
console.log('dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultTableListener(
null,
(queries, tableId) => {
console.log(`${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultRowIdsListener
The addResultRowIdsListener method registers a listener function with the Queries object that will be called whenever the ResultRow Ids in a ResultTable change.
addResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a ResultRowIdsListener function, and will be called with a reference to the Queries object and the Id of the ResultTable that changed (which is also the query Id).
By default, such a listener is only called when a ResultRow is added to, or removed from, the ResultTable. To listen to all changes in the ResultTable, use the addResultTableListener method.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to any change to the ResultRow Ids of a specific ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowIdsListener(
'dogColors',
(queries, tableId) => {
console.log(`Row Ids for dogColors result table changed`);
console.log(queries.getResultRowIds('dogColors'));
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
// -> ['fido', 'cujo', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultRow Ids of any ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowIdsListener(
null,
(queries, tableId) => {
console.log(`Row Ids for ${tableId} result table changed`);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row Ids for catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultSortedRowIdsListener
The addResultSortedRowIdsListener method registers a listener function with the Queries object that will be called whenever sorted (and optionally, paginated) ResultRow Ids in a ResultTable change.
addResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the sorted |
| returns | string | A unique |
The provided listener is a ResultSortedRowIdsListener function, and will be called with a reference to the Queries object, the Id of the ResultTable whose ResultRow Ids sorting changed (which is also the query Id), the ResultCell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getResultSortedRowIds
Such a listener is called when a ResultRow is added or removed, but also when a value in the specified ResultCell (somewhere in the ResultTable) has changed enough to change the sorting of the ResultRow Ids.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified ResultTable, sorted by a single specified ResultCell.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset and limit parameters are used to paginate results, but default to 0 and undefined to return all available ResultRow Ids if not specified.
Examples
This example registers a listener that responds to any change to the sorted ResultRow Ids of a specific ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
'color',
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getResultSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted ResultRow Ids of a specific ResultTable. The ResultRow Ids are sorted by their own value, since the cellId parameter is explicitly undefined.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', undefined));
// -> ['cujo', 'fido']
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
undefined,
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
Since
v2.0.0
addResultRowCountListener
The addResultRowCountListener method registers a listener function with the Queries object that will be called whenever the count of ResultRow objects in a ResultTable changes.
addResultRowCountListener(
queryId: IdOrNull,
listener: ResultRowCountListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowCountListener | The function that will be called whenever the number of |
| returns | string | A unique |
The provided listener is a ResultRowCountListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), and the number of ResultRow objects in th ResultTable.
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Examples
This example registers a listener that responds to a change in the number of ResultRow objects in a specific ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowCountListener(
'dogColors',
(queries, tableId, count) => {
console.log(
'Row count for dogColors result table changed to ' + count,
);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row count for dogColors result table changed to 3'
store.delListener(listenerId);
This example registers a listener that responds to a change in the number of ResultRow objects any ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowCountListener(
null,
(queries, tableId, count) => {
console.log(
`Row count for ${tableId} result table changed to ${count}`,
);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row count for dogColors result table changed to 3'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row count for catColors result table changed to 2'
store.delListener(listenerId);
Since
v4.1.0
addResultRowListener
The addResultRowListener method registers a listener function with the Queries object that will be called whenever data in a ResultRow changes.
addResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching |
| returns | string | A unique |
The provided listener is a ResultRowListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), and a GetResultCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single ResultRow (by specifying a query Id and ResultRow Id as the method's first two parameters) or changes to any ResultRow (by providing null wildcards).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific ResultRow in a specific query, any ResultRow in a specific query, a specific ResultRow in any query, or any ResultRow in any query.
Examples
This example registers a listener that responds to any changes to a specific ResultRow.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowListener(
'dogColors',
'fido',
(queries, tableId, rowId, getCellChange) => {
console.log('fido row in dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultRow.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowListener(
null,
null,
(queries, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellIdsListener
The addResultCellIdsListener method registers a listener function with the Queries object that will be called whenever the ResultCell Ids in a ResultRow change.
addResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
| returns | string | A unique |
The provided listener is a ResultCellIdsListener function, and will be called with a reference to the Queries object, the Id of the ResultTable (which is also the query Id), and the Id of the ResultRow that changed.
Such a listener is only called when a ResultCell is added to, or removed from, the ResultRow. To listen to all changes in the ResultRow, use the addResultRowListener method.
You can either listen to a single ResultRow (by specifying the query Id and ResultRow Id as the method's first two parameters) or changes to any ResultRow (by providing null wildcards).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific ResultRow in a specific query, any ResultRow in a specific query, a specific ResultRow in any query, or any ResultRow in any query.
Examples
This example registers a listener that responds to any change to the ResultCell Ids of a specific ResultRow.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('price');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellIdsListener(
'dogColors',
'fido',
(store, tableId, rowId) => {
console.log(`Cell Ids for fido row in dogColors result table changed`);
console.log(queries.getResultCellIds('dogColors', 'fido'));
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
// -> ['color', 'price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the ResultCell Ids of any ResultRow.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('purrs');
where('species', 'cat');
});
const listenerId = queries.addResultCellIdsListener(
null,
null,
(queries, tableId, rowId) => {
console.log(
`Cell Ids for ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'purrs', true);
// -> 'Cell Ids for felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellListener
The addResultCellListener method registers a listener function with the Queries object that will be called whenever data in a ResultCell changes.
addResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
): string| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching |
| returns | string | A unique |
The provided listener is a ResultCellListener function, and will be called with a reference to the Queries object, the Id of the ResultTable that changed (which is also the query Id), the Id of the ResultRow that changed, the Id of the ResultCell that changed, the new ResultCell value, the old ResultCell value, and a GetResultCellChange function in case you need to inspect any changes that occurred.
You can either listen to a single ResultRow (by specifying a query Id, ResultRow Id, and ResultCell Id as the method's first three parameters) or changes to any ResultCell (by providing null wildcards).
All, some, or none of the queryId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific ResultCell in a specific ResultRow in a specific query, any ResultCell in any ResultRow in any query, for example - or every other combination of wildcards.
Examples
This example registers a listener that responds to any changes to a specific ResultCell.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellListener(
'dogColors',
'fido',
'color',
(queries, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log(
'color cell in fido row in dogColors result table changed',
);
console.log([oldCell, newCell]);
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any ResultCell.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', price: 5},
felix: {species: 'cat', color: 'black', price: 4},
cujo: {species: 'dog', color: 'black', price: 5},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'cat');
});
const listenerId = queries.addResultCellListener(
null,
null,
null,
(queries, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'price', 3);
// -> 'price cell in felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addQueryIdsListener
The addQueryIdsListener method registers a listener function with the Queries object that will be called whenever an Query definition is added or removed.
addQueryIdsListener(listener: QueryIdsListener): string| Type | Description | |
|---|---|---|
listener | QueryIdsListener | The function that will be called whenever a Query definition is added or removed. |
| returns | string |
The provided listener is a QueryIdsListener function, and will be called with a reference to the Queries object.
Example
This example creates a Store, a Queries object, and then registers a listener that responds to the addition and the removal of a Query definition.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
const listenerId = queries.addQueryIdsListener((queries) => {
console.log(queries.getQueryIds());
});
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
// -> []
queries.delListener(listenerId);
Since
v4.1.0
delListener
The delListener method removes a listener that was previously added to the Queries object.
delListener(listenerId: string): Queries| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Queries | A reference to the |
Use the Id returned by the addMetricListener method. Note that the Queries object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Queries object, registers a listener, and then removes it.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store).setQueryDefinition(
'species',
'pets',
({select}) => {
select('species');
},
);
const listenerId = queries.addResultTableListener('species', (queries) =>
console.log('species result changed'),
);
store.setCell('pets', 'ed', 'species', 'horse');
// -> 'species result changed'
queries.delListener(listenerId);
store.setCell('pets', 'molly', 'species', 'cow');
// -> undefined
// The listener is not called.
Since
v2.0.0
Configuration methods
This is the collection of configuration methods within the Queries interface. There are only two configuration methods, delQueryDefinition and setQueryDefinition.
delQueryDefinition
The delQueryDefinition method removes an existing query definition.
delQueryDefinition(queryId: string): Queries| Type | Description | |
|---|---|---|
queryId | string | The |
| returns | Queries | A reference to the |
Example
This example creates a Store, creates a Queries object, defines a simple query, and then removes it.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getQueryIds());
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
console.log(queries.getQueryIds());
// -> []
Since
v2.0.0
setQueryDefinition
The setQueryDefinition method lets you set the definition of a query.
setQueryDefinition(
queryId: string,
tableId: string,
query: (keywords: {
select: Select;
join: Join;
where: Where;
group: Group;
having: Having;
}) => void,
): Queries| Type | Description | |
|---|---|---|
queryId | string | The |
tableId | string | |
query | (keywords: { select: Select; join: Join; where: Where; group: Group; having: Having; }) => void | A callback which can take a |
| returns | Queries | A reference to the |
Every query definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.
A query provides a tabular result formed from each Row within a root Table. The definition must specify this Table (by its Id) to be aggregated. Other Tables can be joined to that using Join clauses.
The third query parameter is a callback that you provide to define the query. That callback is provided with a keywords object that contains the functions you use to define the query, like select, join, and so on. You can see how that is used in the simple example below. The following five clause types are supported:
- The
Selecttype describes a function that lets you specify aCellor calculated value for including into the query's result. - The
Jointype describes a function that lets you specify aCellor calculated value to join the main queryTableto others, byRowId. - The
Wheretype describes a function that lets you specify conditions to filter results, based on the underlying Cells of the main or joinedTables. - The
Grouptype describes a function that lets you specify that the values of aCellin multiple ResultRows should be aggregated together. - The
Havingtype describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from aGroupclause.
Full documentation and examples are provided in the sections for each of those clause types.
Additionally, you can use the getResultSortedRowIds method and addResultSortedRowIdsListener method to sort and paginate the results.
Example
This example creates a Store, creates a Queries object, and defines a simple query to select just one column from the Table, for each Row where the species Cell matches as certain value.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
Since
v2.0.0
Iterator methods
This is the collection of iterator methods within the Queries interface. There are 4 iterator methods in total.
forEachResultTable
The forEachResultTable method takes a function that it will then call for each ResultTable in the Queries object.
forEachResultTable(tableCallback: ResultTableCallback): void| Type | Description | |
|---|---|---|
tableCallback | ResultTableCallback | The function that should be called for every query's |
| returns | void | This has no return value. |
This method is useful for iterating over all the ResultTables of the queries in a functional style. The tableCallback parameter is a ResultTableCallback function that will be called with the Id of each ResultTable, and with a function that can then be used to iterate over each ResultRow of the ResultTable, should you wish.
Example
This example iterates over each query's ResultTable in a Queries object.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachResultTable((queryId, forEachRow) => {
console.log(queryId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dogColors'
// -> '- fido'
// -> '- cujo'
// -> 'catColors'
// -> '- felix'
Since
v2.0.0
forEachResultRow
The forEachResultRow method takes a function that it will then call for each ResultRow in the ResultTable of a query.
forEachResultRow(
queryId: string,
rowCallback: ResultRowCallback,
): void| Type | Description | |
|---|---|---|
queryId | string | The |
rowCallback | ResultRowCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over each ResultRow of the ResultTable of the query in a functional style. The rowCallback parameter is a ResultRowCallback function that will be called with the Id of each ResultRow, and with a function that can then be used to iterate over each ResultCell of the ResultRow, should you wish.
Example
This example iterates over each ResultRow in a query's ResultTable.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
queries.forEachResultRow('dogColors', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- color'
// -> 'cujo'
// -> '- color'
Since
v2.0.0
forEachResultCell
The forEachResultCell method takes a function that it will then call for each ResultCell in the ResultRow of a query.
forEachResultCell(
queryId: string,
rowId: string,
cellCallback: ResultCellCallback,
): void| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
cellCallback | ResultCellCallback | The function that should be called for every |
| returns | void | This has no return value. |
This method is useful for iterating over each ResultCell of the ResultRow of the query in a functional style. The cellCallback parameter is a ResultCellCallback function that will be called with the Id and value of each ResultCell.
Example
This example iterates over each ResultCell in a query's ResultRow.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
where('species', 'dog');
},
);
queries.forEachResultCell('dogColors', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v2.0.0
forEachQuery
The forEachQuery method takes a function that it will then call for each Query in the Queries object.
forEachQuery(queryCallback: QueryCallback): void| Type | Description | |
|---|---|---|
queryCallback | QueryCallback | The function that should be called for every query. |
| returns | void | This has no return value. |
This method is useful for iterating over all the queries in a functional style. The queryCallback parameter is a QueryCallback function that will be called with the Id of each query.
Example
This example iterates over each query in a Queries object.
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachQuery((queryId) => {
console.log(queryId);
});
// -> 'dogColors'
// -> 'catColors'
Since
v2.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Queries interface. There is only one method, destroy.
destroy
The destroy method should be called when this Queries object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Queries object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('species', 'species', ({select}) => {
select('species');
});
console.log(store.getListenerStats().row);
// -> 1
queries.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v2.0.0
Development methods
This is the collection of development methods within the Queries interface. There is only one method, getListenerStats.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Queries object, and is used for debugging purposes.
getListenerStats(): QueriesListenerStats| returns | QueriesListenerStats | A |
|---|
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Queries object.
const store = createStore();
const queries = createQueries(store);
queries.addResultTableListener(null, () => console.log('Result changed'));
console.log(queries.getListenerStats().table);
// -> 1
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
Functions
There is one function, createQueries, within the queries module.
createQueries
The createQueries function creates a Queries object, and is the main entry point into the queries module.
createQueries(store: Store): Queries| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Queries | A reference to the new |
A given Store can only have one Queries object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Queries object created by the first.
Examples
This example creates a Queries object.
const store = createStore();
const queries = createQueries(store);
console.log(queries.getQueryIds());
// -> []
This example creates a Queries object, and calls the method a second time for the same Store to return the same object.
const store = createStore();
const queries1 = createQueries(store);
const queries2 = createQueries(store);
console.log(queries1 === queries2);
// -> true
Since
v2.0.0
Type Aliases
These are the type aliases within the queries module.
Definition type aliases
This is the collection of definition type aliases within the queries module. There are 8 definition type aliases in total.
Group
The Group type describes a function that lets you specify that the values of a Cell in multiple ResultRows should be aggregated together.
(
selectedCellId: Id,
aggregate: "count" | "sum" | "avg" | "min" | "max" | Aggregate,
aggregateAdd?: AggregateAdd,
aggregateRemove?: AggregateRemove,
aggregateReplace?: AggregateReplace,
): GroupedAs| Type | Description | |
|---|---|---|
selectedCellId | Id | The |
aggregate | "count" | "sum" | "avg" | "min" | "max" | Aggregate | Either a string representing one of a set of common aggregation techniques ('count', 'sum', 'avg', 'min', or 'max'), or a function that aggregates |
aggregateAdd? | AggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | AggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | AggregateReplace | A function that can be used to optimize a custom |
| returns | GroupedAs | A |
The Group function is provided to the third query parameter of the setQueryDefinition method. When called, it should refer to a Cell Id (or aliased Id) specified in one of the Select functions, and indicate how the values should be aggregated.
This is applied after any joins or where-based filtering.
If you provide a Group for every Select, the result will be a single Row with every Cell having been aggregated. If you provide a Group for only one, or some, of the Select clauses, the others will be automatically used as dimensional values (analogous to the 'group bysemantics in SQL), within which the aggregations ofGroup` Cells will be performed.
You can join the same underlying Cell multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
The second parameter can be one of five predefined aggregates - 'count', 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of Cell values.
The final three parameters, aggregateAdd, aggregateRemove, aggregateReplace need only be provided when you are using your own custom aggregate function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.
Examples
This example shows a query that calculates the average of all the values in a single selected Cell from a joined Table.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
lowly: {species: 'worm'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('species', 'price');
// from pets
join('species', 'species');
group('price', 'avg').as('avgPrice');
});
console.log(queries.getResultTable('query'));
// -> {0: {avgPrice: 3.75}}
// 2 dogs at 5, 1 cat at 4, 1 worm at 1: a total of 15 for 4 pets
This example shows a query that calculates the average of a two Cell values, aggregated by the two other dimensional 'group by' Cells.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown', owner: 'alice'},
felix: {species: 'cat', color: 'black', owner: 'bob'},
cujo: {species: 'dog', color: 'black', owner: 'bob'},
lowly: {species: 'worm', color: 'brown', owner: 'alice'},
carnaby: {species: 'parrot', color: 'black', owner: 'bob'},
polly: {species: 'parrot', color: 'red', owner: 'alice'},
})
.setTable('species', {
dog: {price: 5, legs: 4},
cat: {price: 4, legs: 4},
parrot: {price: 3, legs: 2},
worm: {price: 1, legs: 0},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('pets', 'color'); // group by
select('pets', 'owner'); // group by
select('species', 'price'); // grouped
select('species', 'legs'); // grouped
// from pets
join('species', 'species');
group('price', 'avg').as('avgPrice');
group('legs', 'sum').as('sumLegs');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {color: 'brown', owner: 'alice', avgPrice: 3, sumLegs: 4}}
// -> {1: {color: 'black', owner: 'bob', avgPrice: 4, sumLegs: 10}}
// -> {2: {color: 'red', owner: 'alice', avgPrice: 3, sumLegs: 2}}
This example shows a query that calculates the a custom aggregate of one Cell's values, grouped by another. Note how aggregateAdd, aggregateRemove, and aggregateReplace parameters are provided to make the custom aggregation more efficient as individual values are added, removed, or replaced during the lifecycle of the Table.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', owner: 'alice'},
felix: {species: 'cat', owner: 'bob'},
cujo: {species: 'dog', owner: 'bob'},
lowly: {species: 'worm', owner: 'alice'},
carnaby: {species: 'parrot', owner: 'bob'},
polly: {species: 'parrot', owner: 'alice'},
})
.setTable('species', {
dog: {price: 5, legs: 4},
cat: {price: 4, legs: 4},
parrot: {price: 3, legs: 2},
worm: {price: 1, legs: 0},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('pets', 'owner'); // group by
select('species', 'price'); // grouped
// from pets
join('species', 'species');
group(
'price',
(cells) => Math.min(...cells.filter((cell) => cell > 2)),
(current, add) => (add > 2 ? Math.min(current, add) : current),
(current, remove) => (remove == current ? undefined : current),
(current, add, remove) =>
remove == current
? undefined
: add > 2
? Math.min(current, add)
: current,
).as('lowestPriceOver2');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {owner: 'alice', lowestPriceOver2: 3}}
// -> {1: {owner: 'bob', lowestPriceOver2: 3}}
// Both have a parrot at 3. Alice's worm at 1 is excluded from aggregation.
Since
v2.0.0
GroupedAs
The GroupedAs type describes an object returned from calling a Group function so that the grouped Cell Id can be optionally aliased.
{as: (groupedCellId: string): void}| Type | Description | |
|---|---|---|
as | as(groupedCellId: string): void | A function that lets you specify an alias for the grouped |
Note that if two Group clauses are both aliased to the same name (or if you create two groups of the same underlying Cell, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that groups the same underlying Cell twice, for different purposes. Both groups are aliased with the 'as' function to disambiguate them.
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
Since
v2.0.0
Having
The Having type describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from a Group clause.
Calling this function with two parameters is used to include only those Rows for which a specified Cell in the query's root Table has a specified value.
(
selectedOrGroupedCellId: string,
equals: Cell,
): void| Type | Description | |
|---|---|---|
selectedOrGroupedCellId | string | |
equals | Cell | The value that the |
| returns | void | This has no return value. |
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition.
(condition: (getSelectedOrGroupedCell: GetCell) => boolean): void| Type | Description | |
|---|---|---|
condition | (getSelectedOrGroupedCell: GetCell) => boolean | A callback that takes a |
| returns | void | This has no return value. |
The Having function is provided to the third query parameter of the setQueryDefinition method.
A Having condition has to be true for a Row to be included in the results. Each Having class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where keyword differs from the Having keyword in that the former describes conditions that should be met by underlying Cell values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group clauses have been applied.
Whilst it is technically possible to use a Having clause even if the results have not been grouped with a Group clause, you should expect it to be less performant than using a Where clause, due to that being applied earlier in the query process.
Examples
This example shows a query that filters the results from a grouped Table by comparing a Cell from it with a value.
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having('minPrice', 3);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'cat', minPrice: 3, maxPrice: 4}}
// -> {1: {species: 'parrot', minPrice: 3, maxPrice: 3}}
This example shows a query that filters the results from a grouped Table with a condition that is calculated from Cell values.
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having(
(getSelectedOrGroupedCell) =>
getSelectedOrGroupedCell('minPrice') !=
getSelectedOrGroupedCell('maxPrice'),
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
// Parrots are filtered out because they have zero range in price.
Since
v2.0.0
Join
The Join type describes a function that lets you specify a Cell or calculated value to join the main query Table to other Tables, by their Row Id.
Calling this function with two Id parameters will indicate that the join to a Row in an adjacent Table is made by finding its Id in a Cell of the query's root Table.
(
joinedTableId: string,
on: string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
on | string | The |
| returns | JoinedAs | A |
Calling this function with two parameters (where the second is a function) will indicate that the join to a Row in an adjacent Table is made by calculating its Id from the Cells and the Row Id of the query's root Table.
(
joinedTableId: string,
on: (getCell: GetCell, rowId: string) => undefined | string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
on | (getCell: GetCell, rowId: string) => undefined | string | A callback that takes a |
| returns | JoinedAs | A |
Calling this function with three Id parameters will indicate that the join to a Row in distant Table is made by finding its Id in a Cell of an intermediately joined Table.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | string | The |
| returns | JoinedAs | A |
Calling this function with three parameters (where the third is a function) will indicate that the join to a Row in distant Table is made by calculating its Id from the Cells and the Row Id of an intermediately joined Table.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: (getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: string) => undefined | string,
): JoinedAs| Type | Description | |
|---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | (getIntermediateJoinedCell: GetCell, intermediateJoinedRowId: string) => undefined | string | A callback that takes a |
| returns | JoinedAs | A |
The Join function is provided to the third query parameter of the setQueryDefinition method.
You can join zero, one, or many Tables. You can join the same underlying Table multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
By default, each join is made from the main query Table to the joined table, but it is also possible to connect via an intermediate join Table to a more distant join Table.
Because a Join clause is used to identify which unique Row Id of the joined Table will be joined to each Row of the root Table, queries follow the 'left join' semantics you may be familiar with from SQL. This means that an unfiltered query will only ever return the same number of Rows as the main Table being queried, and indeed the resulting table (assuming it has not been aggregated) will even preserve the root Table's original Row Ids.
Examples
This example shows a query that joins a single Table by using an Id present in the main query Table.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that joins the same underlying Table twice, and aliases them (and the selected Cell Ids). Note the left-join semantics: Felix the cat was bought, but the seller was unknown. The record still exists in the ResultTable.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
This example shows a query that calculates the Id of the joined Table based from multiple values in the root Table rather than a single Cell.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('colorSpecies', {
'brown-dog': {price: 6},
'black-dog': {price: 5},
'brown-cat': {price: 4},
'black-cat': {price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('colorSpecies', 'price');
// from pets
join(
'colorSpecies',
(getCell) => `${getCell('color')}-${getCell('species')}`,
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {price: 6}}
// -> {felix: {price: 3}}
// -> {cujo: {price: 5}}
This example shows a query that joins two Tables, one through the intermediate other.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
})
.setTable('states', {
CA: {name: 'California'},
WA: {name: 'Washington'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell, rowId) =>
`${getTableCell('species')} in ${getTableCell('states', 'name')}`,
).as('description');
// from pets
join('owners', 'ownerId');
join('states', 'owners', 'state');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog in California'}}
// -> {felix: {description: 'cat in California'}}
// -> {cujo: {description: 'dog in Washington'}}
Since
v2.0.0
JoinedAs
The JoinedAs type describes an object returned from calling a Join function so that the joined Table Id can be optionally aliased.
{as: (joinedTableId: string): void}| Type | Description | |
|---|---|---|
as | as(joinedTableId: string): void | A function that lets you specify an alias for the joined |
Note that if two Join clauses are both aliased to the same name (or if you create two joins to the same underlying Table, both without aliases), only the latter of two will be used in the query.
For the purposes of clarity, it's recommended to use an alias that does not collide with a real underlying Table (whether included in the query or not).
Example
This example shows a query that joins the same underlying Table twice, for different purposes. Both joins are aliased with the 'as' function to disambiguate them. Note that the selected Cells are also aliased.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
Since
v2.0.0
Select
The Select type describes a function that lets you specify a Cell or calculated value for including into the query's result.
Calling this function with one Id parameter will indicate that the query should select the value of the specified Cell from the query's root Table.
(cellId: string): SelectedAs| Type | Description | |
|---|---|---|
cellId | string | |
| returns | SelectedAs | A |
Calling this function with two parameters will indicate that the query should select the value of the specified Cell from a Table that has been joined in the query.
(
joinedTableId: string,
joinedCellId: string,
): SelectedAs| Type | Description | |
|---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
| returns | SelectedAs | A |
Calling this function with one callback parameter will indicate that the query should select a calculated value, based on one or more Cell values in the root Table or a joined Table, or on the root Table's Row Id.
(getCell: (getTableCell: GetTableCell, rowId: string) => ResultCellOrUndefined): SelectedAs| Type | Description | |
|---|---|---|
getCell | (getTableCell: GetTableCell, rowId: string) => ResultCellOrUndefined | A callback that takes a |
| returns | SelectedAs | A |
The Select function is provided to the third query parameter of the setQueryDefinition method. A query definition must call the Select function at least once, otherwise it will be meaningless and return no data.
Examples
This example shows a query that selects two Cells from the main query Table.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', legs: 4},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select}) => {
select('species');
select('color');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', color: 'brown'}}
// -> {felix: {species: 'cat', color: 'black'}}
// -> {cujo: {species: 'dog', color: 'black'}}
This example shows a query that selects two Cells, one from a joined Table.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that calculates a value from two underlying Cells.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell, rowId) =>
`${getTableCell('species')} for ${getTableCell('owners', 'name')}`,
).as('description');
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog for Alice'}}
// -> {felix: {description: 'cat for Bob'}}
// -> {cujo: {description: 'dog for Carol'}}
Since
v2.0.0
SelectedAs
The SelectedAs type describes an object returned from calling a Select function so that the selected Cell Id can be optionally aliased.
{as: (selectedCellId: string): void}| Type | Description | |
|---|---|---|
as | as(selectedCellId: string): void |
If you are using a callback in the Select cause, it is highly recommended to use the 'as' function, since otherwise a machine-generated column name will be used.
Note that if two Select clauses are both aliased to the same name (or if two columns with the same underlying name are selected, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that selects two Cells, one from a joined Table. Both are aliased with the 'as' function:
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species').as('petSpecies');
select('owners', 'name').as('ownerName');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {petSpecies: 'dog', ownerName: 'Alice'}}
// -> {felix: {petSpecies: 'cat', ownerName: 'Bob'}}
// -> {cujo: {petSpecies: 'dog', ownerName: 'Carol'}}
Since
v2.0.0
Where
The Where type describes a function that lets you specify conditions to filter results, based on the underlying Cells of the root or joined Tables.
Calling this function with two parameters is used to include only those Rows for which a specified Cell in the query's root Table has a specified value.
(
cellId: string,
equals: Cell,
): void| Type | Description | |
|---|---|---|
cellId | string | |
equals | Cell | The value that the |
| returns | void | This has no return value. |
Calling this function with three parameters is used to include only those Rows for which a specified Cell in a joined Table has a specified value.
(
joinedTableId: string,
joinedCellId: string,
equals: Cell,
): void| Type | Description | |
|---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
equals | Cell | The value that the |
| returns | void | This has no return value. |
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition, based on values in the main and (optionally) joined Tables.
(condition: (getTableCell: GetTableCell) => boolean): void| Type | Description | |
|---|---|---|
condition | (getTableCell: GetTableCell) => boolean | A callback that takes a |
| returns | void | This has no return value. |
The Where function is provided to the third query parameter of the setQueryDefinition method.
If you do not specify a Where clause, you should expect every non-empty Row of the root Table to appear in the query's results.
A Where condition has to be true for a Row to be included in the results. Each Where class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where keyword differs from the Having keyword in that the former describes conditions that should be met by underlying Cell values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group clauses have been applied.
Examples
This example shows a query that filters the results from a single Table by comparing an underlying Cell from it with a value.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, where}) => {
select('species');
where('species', 'dog');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {cujo: {species: 'dog'}}
This example shows a query that filters the results of a query by comparing an underlying Cell from a joined Table with a value. Note that the joined table has also been aliased, and so its alias is used in the Where clause.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
// from pets
join('owners', 'ownerId').as('petOwners');
where('petOwners', 'state', 'CA');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {felix: {species: 'cat'}}
This example shows a query that filters the results of a query with a condition that is calculated from underlying Cell values from the main and joined Table. Note that the joined table has also been aliased, and so its alias is used in the Where clause.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
select('petOwners', 'state');
// from pets
join('owners', 'ownerId').as('petOwners');
where(
(getTableCell) =>
getTableCell('pets', 'species') === 'cat' ||
getTableCell('petOwners', 'state') === 'WA',
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {felix: {species: 'cat', state: 'CA'}}
// -> {cujo: {species: 'dog', state: 'WA'}}
Since
v2.0.0
Result type aliases
This is the collection of result type aliases within the queries module. There are 4 result type aliases in total.
ResultTable
The ResultTable type is the data structure representing the results of a query.
{[rowId: Id]: ResultRow}A ResultTable is typically accessed with the getResultTable method or addResultTableListener method. It is similar to the Table type in the store module, but without schema-specific typing, and is a regular JavaScript object containing individual ResultRow objects, keyed by their Id.
Example
const resultTable: ResultTable = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
ResultRow
The ResultRow type is the data structure representing a single row in the results of a query.
{[cellId: Id]: ResultCell}A ResultRow is typically accessed with the getResultRow method or addResultRowListener method. It is similar to the Row type in the store module, but without schema-specific typing, and is a regular JavaScript object containing individual ResultCell objects, keyed by their Id.
Example
const resultRow: ResultRow = {species: 'dog', color: 'brown'};
ResultCell
The ResultCell type is the data structure representing a single cell in the results of a query.
string | number | booleanA ResultCell is typically accessed with the getResultCell method or addResultCellListener method. It is similar to the Cell type in the store module, but without schema-specific typing, and is a JavaScript string, number, or boolean.
Example
const resultCell: ResultCell = 'dog';
ResultCellOrUndefined
The ResultCellOrUndefined type is the data structure representing a single cell in the results of a query, or the value undefined.
ResultCell | undefinedListener type aliases
This is the collection of listener type aliases within the queries module. There are 11 listener type aliases in total.
ResultTableCellIdsListener
The ResultTableCellIdsListener type describes a function that is used to listen to changes to the Cell Ids that appear anywhere in a query's ResultTable.
(
queries: Queries,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getIdChanges | GetIdChanges | undefined | |
| returns | void | This has no return value. |
A ResultTableCellIdsListener is provided when using the addResultTableCellIdsListener method. See that method for specific examples.
When called, a ResultTableCellIdsListener is given a reference to the Queries object, and the Id of the ResultTable whose Cell Ids changed (which is the same as the query Id).
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v4.1.0
ResultTableListener
The ResultTableListener type describes a function that is used to listen to changes to a query's ResultTable.
(
queries: Queries,
tableId: Id,
getCellChange: GetResultCellChange,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getCellChange | GetResultCellChange | A function that returns information about any |
| returns | void | This has no return value. |
A ResultTableListener is provided when using the addResultTableListener method. See that method for specific examples.
When called, a ResultTableListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), and a GetResultCellChange function that can be used to query ResultCell values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultRowIdsListener
The ResultRowIdsListener type describes a function that is used to listen to changes to the ResultRow Ids in a query's ResultTable.
(
queries: Queries,
tableId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getIdChanges | GetIdChanges | undefined | |
| returns | void | This has no return value. |
A ResultRowIdsListener is provided when using the addResultRowIdsListener method. See that method for specific examples.
When called, a ResultRowIdsListener is given a reference to the Queries object, and the Id of the ResultTable whose ResultRow Ids changed (which is the same as the query Id).
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultSortedRowIdsListener
The ResultSortedRowIdsListener type describes a function that is used to listen to changes to the sorted ResultRow Ids in a query's ResultTable.
(
queries: Queries,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
cellId | Id | undefined | The |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
| returns | void | This has no return value. |
A ResultSortedRowIdsListener is provided when using the addResultSortedRowIdsListener method. See that method for specific examples.
When called, a ResultSortedRowIdsListener is given a reference to the Queries object, the Id of the ResultTable whose ResultRow Ids changed (which is the same as the query Id), the ResultCell Id being used to sort them, whether descending or not, and the offset and limit of the number of Ids returned, for pagination purposes. It also receives the sorted array of Ids itself, so that you can use them in the listener without the additional cost of an explicit call to getResultSortedRowIds.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultRowCountListener
The ResultRowCountListener type describes a function that is used to listen to changes to the number of ResultRow objects in a query's ResultTable.
(
queries: Queries,
tableId: Id,
count: number,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
count | number | The number of |
| returns | void | This has no return value. |
A ResultRowCountListener is provided when using the addResultRowCountListener method. See that method for specific examples.
When called, a ResultRowCountListener is given a reference to the Queries object, the Id of the ResultTable whose ResultRow Ids changed (which is the same as the query Id), and the count of ResultRow objects in the ResultTable.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v4.1.0
ResultRowListener
The ResultRowListener type describes a function that is used to listen to changes to a ResultRow in a query's ResultTable.
(
queries: Queries,
tableId: Id,
rowId: Id,
getCellChange: GetResultCellChange,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getCellChange | GetResultCellChange | A function that returns information about any |
| returns | void | This has no return value. |
A ResultRowListener is provided when using the addResultRowListener method. See that method for specific examples.
When called, a ResultRowListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), the Id of the ResultRow that changed, and a GetResultCellChange function that can be used to query ResultCell values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
ResultCellIdsListener
The ResultCellIdsListener type describes a function that is used to listen to changes to the ResultCell Ids in a ResultRow in a query's ResultTable.
(
queries: Queries,
tableId: Id,
rowId: Id,
getIdChanges: GetIdChanges | undefined,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getIdChanges | GetIdChanges | undefined | |
| returns | void | This has no return value. |
A ResultCellIdsListener is provided when using the addResultCellIdsListener method. See that method for specific examples.
When called, a ResultCellIdsListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), and the Id of the ResultRow whose ResultCell Ids changed.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
GetResultCellChange
The GetResultCellChange type describes a function that returns information about any ResultCell's changes during a transaction.
(
tableId: Id,
rowId: Id,
cellId: Id,
): ResultCellChange| Type | Description | |
|---|---|---|
tableId | Id | The |
rowId | Id | |
cellId | Id | The |
| returns | ResultCellChange | A |
A GetResultCellChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a ResultCell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
ResultCellChange
The ResultCellChange type describes a ResultCell's changes during a transaction.
[changed: boolean, oldCell: ResultCellOrUndefined, newCell: ResultCellOrUndefined]This is returned by the GetResultCellChange function that is provided to every listener when called. This array contains the previous value of a ResultCell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
ResultCellListener
The ResultCellListener type describes a function that is used to listen to changes to a ResultCell in a query's ResultTable.
(
queries: Queries,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: ResultCell,
oldCell: ResultCell,
getCellChange: GetResultCellChange,
): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
cellId | Id | The |
newCell | ResultCell | The new value of the |
oldCell | ResultCell | The old value of the |
getCellChange | GetResultCellChange | A function that returns information about any |
| returns | void | This has no return value. |
A ResultCellListener is provided when using the addResultCellListener method. See that method for specific examples.
When called, a ResultCellListener is given a reference to the Queries object, the Id of the ResultTable that changed (which is the same as the query Id), the Id of the ResultRow that changed, and the Id of ResultCell that changed. It is also given the new value of the ResultCell, the old value of the ResultCell, and a GetResultCellChange function that can be used to query ResultCell values before and after the change.
You can create new query definitions within the body of this listener, though obviously be aware of the possible cascading effects of doing so.
Since
v2.0.0
QueryIdsListener
The QueryIdsListener type describes a function that is used to listen to Query definitions being added or removed.
(queries: Queries): void| Type | Description | |
|---|---|---|
queries | Queries | A reference to the |
| returns | void | This has no return value. |
A QueryIdsListener is provided when using the addQueryIdsListener method. See that method for specific examples.
When called, a QueryIdsListener is given a reference to the Queries object.
Aggregators type aliases
This is the collection of aggregators type aliases within the queries module. There are 4 aggregators type aliases in total.
Aggregate
The Aggregate type describes a custom function that takes an array of Cell values and returns an aggregate of them.
(
cells: Cell[],
length: number,
): ResultCell| Type | Description | |
|---|---|---|
cells | Cell[] | The array of |
length | number | The length of the array of |
| returns | ResultCell | The value of the aggregation. |
There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising.
Since
v2.0.0
AggregateAdd
The AggregateAdd type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is added to the input values.
(
current: Cell,
add: Cell,
length: number,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
length | number | The length of the array of |
| returns | ResultCellOrUndefined | The new value of the aggregation. |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.
If it is not possible to shortcut the aggregation based on just one value being added, return undefined and the aggregation will be completely recalculated.
Where possible, if you are providing a custom Aggregate, seek an implementation of an AggregateAdd function that can reduce the complexity cost of growing the input data set.
Since
v2.0.0
AggregateRemove
The AggregateRemove type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is removed from the input values.
(
current: Cell,
remove: Cell,
length: number,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
current | Cell | The current value of the aggregation. |
remove | Cell | The |
length | number | The length of the array of |
| returns | ResultCellOrUndefined | The new value of the aggregation. |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.
If it is not possible to shortcut the aggregation based on just one value being removed, return undefined and the aggregation will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.
Where possible, if you are providing a custom Aggregate, seek an implementation of an AggregateRemove function that can reduce the complexity cost of shrinking the input data set.
Since
v2.0.0
AggregateReplace
The AggregateReplace type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value in the input values is replaced with another.
(
current: Cell,
add: Cell,
remove: Cell,
length: number,
): ResultCellOrUndefined| Type | Description | |
|---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
remove | Cell | The |
length | number | The length of the array of |
| returns | ResultCellOrUndefined | The new value of the aggregation. |
Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.
If it is not possible to shortcut the aggregation based on just one value changing, return undefined and the aggregation will be completely recalculated.
Where possible, if you are providing a custom Aggregate, seek an implementation of an AggregateReplace function that can reduce the complexity cost of changing the input data set in place.
Since
v2.0.0
Callback type aliases
This is the collection of callback type aliases within the queries module. There are 5 callback type aliases in total.
GetTableCell
The GetTableCell type describes a function that takes a Id and returns the Cell value for a particular Row, optionally in a joined Table.
When called with one parameter, this function will return the value of the specified Cell from the query's root Table for the Row being selected or filtered.
(cellId: string): CellOrUndefined| Type | Description | |
|---|---|---|
cellId | string | |
| returns | CellOrUndefined | A |
When called with two parameters, this function will return the value of the specified Cell from a Table that has been joined in the query, for the Row being selected or filtered.
(
joinedTableId: string,
joinedCellId: string,
): CellOrUndefined| Type | Description | |
|---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
| returns | CellOrUndefined | A |
A GetTableCell can be provided when setting query definitions, specifically in the Select and Where clauses when you want to create or filter on calculated values. See those methods for specific examples.
Since
v2.0.0
ResultTableCallback
The ResultTableCallback type describes a function that takes a ResultTable's Id and a callback to loop over each ResultRow within it.
(
tableId: Id,
forEachRow: (rowCallback: ResultRowCallback) => void,
): void| Type | Description | |
|---|---|---|
tableId | Id | The |
forEachRow | (rowCallback: ResultRowCallback) => void | A function that will let you iterate over the |
| returns | void | This has no return value. |
A ResultTableCallback is provided when using the forEachResultTable method, so that you can do something based on every ResultTable in the Queries object. See that method for specific examples.
ResultRowCallback
The ResultRowCallback type describes a function that takes a ResultRow's Id and a callback to loop over each ResultCell within it.
(
rowId: Id,
forEachCell: (cellCallback: ResultCellCallback) => void,
): void| Type | Description | |
|---|---|---|
rowId | Id | |
forEachCell | (cellCallback: ResultCellCallback) => void | |
| returns | void | This has no return value. |
A ResultRowCallback is provided when using the forEachResultRow method, so that you can do something based on every ResultRow in a ResultTable. See that method for specific examples.
ResultCellCallback
The ResultCellCallback type describes a function that takes a ResultCell's Id and its value.
(
cellId: Id,
cell: ResultCell,
): void| Type | Description | |
|---|---|---|
cellId | Id | The |
cell | ResultCell | The value of the |
| returns | void | This has no return value. |
A ResultCellCallback is provided when using the forEachResultCell method, so that you can do something based on every ResultCell in a ResultRow. See that method for specific examples.
QueryCallback
The QueryCallback type describes a function that takes a query's Id.
(queryId: Id): void| Type | Description | |
|---|---|---|
queryId | Id | The |
| returns | void | This has no return value. |
A QueryCallback is provided when using the forEachQuery method, so that you can do something based on every query in the Queries object. See that method for specific examples.
Since
v2.0.0
Development type aliases
This is the collection of development type aliases within the queries module. There is only one type alias, QueriesListenerStats.
QueriesListenerStats
The QueriesListenerStats type describes the number of listeners registered with the Queries object, and can be used for debugging purposes.
{
table?: number;
tableCellIds?: number;
rowCount?: number;
rowIds?: number;
sortedRowIds?: number;
row?: number;
cellIds?: number;
cell?: number;
}| Type | Description | |
|---|---|---|
table? | number | The number of |
tableCellIds? | number | The number of |
rowCount? | number | The number of |
rowIds? | number | The number of |
sortedRowIds? | number | The number of |
row? | number | The number of |
cellIds? | number | The number of |
cell? | number | The number of |
A QueriesListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.
Since
v2.0.0
checkpoints
The checkpoints module of the TinyBase project provides the ability to create and track checkpoints made to the data in Store objects.
The main entry point to this module is the createCheckpoints function, which returns a new Checkpoints object. From there, you can create new checkpoints, go forwards or backwards to others, and register listeners for when the list of checkpoints change.
Interfaces
There is one interface, Checkpoints, within the checkpoints module.
Checkpoints
A Checkpoints object lets you set checkpoints on a Store, and move forward and backward through them to create undo and redo functionality.
Create a Checkpoints object easily with the createCheckpoints function. From there, you can set checkpoints (with the addCheckpoint method), query the checkpoints available (with the getCheckpointIds method), move forward and backward through them (with the goBackward method, goForward method, and goTo method), and add listeners for when the list checkpoints changes (with the addCheckpointIdsListener method).
Checkpoints work for both changes to tabular data and to keyed value data.
Every checkpoint can be given a label which can be used to describe the actions that changed the Store before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Example
This example shows a simple lifecycle of a Checkpoints object: from creation, to adding a checkpoint, getting the list of available checkpoints, and then registering and removing a listener for them.
const store = createStore()
.setTables({pets: {fido: {sold: false}}})
.setValue('open', true);
const checkpoints = createCheckpoints(store);
checkpoints.setSize(200);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
store.setValue('open', false);
checkpoints.addCheckpoint('closed');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '2', []]
checkpoints.goBackward();
console.log(store.getValue('open'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['2']]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> [['0'], '3', []]
// Previous redo of checkpoints '1' and '2' are now not possible.
checkpoints.delListener(listenerId);
checkpoints.destroy();
See also
Relationships And Checkpoints guides
Todo App demos
Drawing demo
Getter methods
This is the collection of getter methods within the Checkpoints interface. There are 4 getter methods in total.
getStore
The getStore method returns a reference to the underlying Store that is backing this Checkpoints object.
getStore(): StoreExample
This example creates a Checkpoints object against a newly-created Store and then gets its reference in order to update its data and set a checkpoint.
const checkpoints = createCheckpoints(createStore());
checkpoints.getStore().setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
getCheckpoint
The getCheckpoint method fetches the label for a checkpoint, if it had been provided at the time of the addCheckpoint method or set subsequently with the setCheckpoint method.
getCheckpoint(checkpointId: string): undefined | string| Type | Description | |
|---|---|---|
checkpointId | string | The |
| returns | undefined | string | A string label for the requested checkpoint, an empty string if it was never set, or |
If the checkpoint has had no label provided, this method will return an empty string.
Examples
This example creates a Store, adds a Checkpoints object, and sets a checkpoint with a label, before retrieving it again.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
console.log(checkpoints.addCheckpoint('sale'));
// -> '1'
console.log(checkpoints.getCheckpoint('1'));
// -> 'sale'
This example creates a Store, adds a Checkpoints object, and sets a checkpoint without a label, setting it subsequently. A non-existent checkpoint return an undefined label.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpoint('1'));
// -> ''
checkpoints.setCheckpoint('1', 'sold');
console.log(checkpoints.getCheckpoint('1'));
// -> 'sold'
console.log(checkpoints.getCheckpoint('2'));
// -> undefined
getCheckpointIds
The getCheckpointIds method returns an array of the checkpoint Ids being managed by this Checkpoints object.
getCheckpointIds(): CheckpointIds| returns | CheckpointIds | A |
|---|
The returned CheckpointIds array contains 'backward' checkpoint Ids, the current checkpoint Id (if present), and the 'forward' checkpointIds. Together, these are sufficient to understand the state of the Checkpoints object and what movement is possible backward or forward through the checkpoint stack.
Example
This example creates a Store, adds a Checkpoints object, and then gets the Ids of the checkpoints as it sets them and moves around the stack.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
checkpoints.goForward();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
hasCheckpoint
The hasCheckpoint method returns a boolean indicating whether a given Checkpoint exists in the Checkpoints object.
hasCheckpoint(checkpointId: string): boolean| Type | Description | |
|---|---|---|
checkpointId | string | The |
| returns | boolean | Whether a Checkpoint with that |
Example
This example shows two simple Checkpoint existence checks.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.hasCheckpoint('0'));
// -> true
console.log(checkpoints.hasCheckpoint('1'));
// -> false
Setter methods
This is the collection of setter methods within the Checkpoints interface. There are only two setter methods, addCheckpoint and setCheckpoint.
addCheckpoint
The addCheckpoint method records a checkpoint of the Store into the Checkpoints object that can be reverted to in the future.
addCheckpoint(label?: string): string| Type | Description | |
|---|---|---|
label? | string | An optional label to describe the actions leading up to this checkpoint. |
| returns | string | The |
If no changes have been made to the Store since the last time a checkpoint was made, this method will have no effect.
The optional label parameter can be used to describe the actions that changed the Store before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Example
This example creates a Store, adds a Checkpoints object, and adds two checkpoints, one with a label.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'species', 'dog');
const checkpointId1 = checkpoints.addCheckpoint();
console.log(checkpointId1);
// -> '1'
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
setCheckpoint
The setCheckpoint method updates the label for a checkpoint in the Checkpoints object after it has been created
setCheckpoint(
checkpointId: string,
label: string,
): Checkpoints| Type | Description | |
|---|---|---|
checkpointId | string | The |
label | string | A label to describe the actions leading up to this checkpoint or left undefined if you want to clear the current label. |
| returns | Checkpoints | A reference to the |
The label parameter can be used to describe the actions that changed the Store before the given checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.
Generally you will provide the label parameter when the addCheckpoint method is called. Use this setCheckpoint method only when you need to change the label at a later point.
You cannot add a label to a checkpoint that does not yet exist.
Example
This example creates a Store, adds a Checkpoints object, and sets two checkpoints, one with a label, which are both then re-labelled.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpoint('1'));
// -> ''
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
checkpoints.setCheckpoint('1', 'identified');
checkpoints.setCheckpoint('2', '');
console.log(checkpoints.getCheckpoint('1'));
// -> 'identified'
console.log(checkpoints.getCheckpoint('2'));
// -> ''
checkpoints.setCheckpoint('3', 'unknown');
console.log(checkpoints.getCheckpoint('3'));
// -> undefined
Listener methods
This is the collection of listener methods within the Checkpoints interface. There are only three listener methods, addCheckpointIdsListener, addCheckpointListener, and delListener.
addCheckpointIdsListener
The addCheckpointIdsListener method registers a listener function with the Checkpoints object that will be called whenever its set of checkpoints changes.
addCheckpointIdsListener(listener: CheckpointIdsListener): string| Type | Description | |
|---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
| returns | string | A unique |
The provided listener is a CheckpointIdsListener function, and will be called with a reference to the Checkpoints object.
Example
This example creates a Store, a Checkpoints object, and then registers a listener that responds to any changes to the checkpoints.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('Checkpoint Ids changed');
console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Checkpoint Ids changed'
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]
checkpoints.goBackward();
// -> 'Checkpoint Ids changed'
// -> [[], '0', ['1']]
checkpoints.goForward();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]
checkpoints.delListener(listenerId);
checkpoints.destroy();
addCheckpointListener
The addCheckpointListener method registers a listener function with the Checkpoints object that will be called whenever the label of a checkpoint changes.
addCheckpointListener(
checkpointId: IdOrNull,
listener: CheckpointListener,
): string| Type | Description | |
|---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
| returns | string | A unique |
You can either listen to a single checkpoint label (by specifying the checkpoint Id as the method's first parameter), or changes to any checkpoint label (by providing a null wildcard).
The provided listener is a CheckpointListener function, and will be called with a reference to the Checkpoints object, and the Id of the checkpoint whose label changed.
Example
This example creates a Store, a Checkpoints object, and then registers a listener that responds to any changes to a specific checkpoint label, including when the checkpoint no longer exists.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointListener('1', () => {
console.log('Checkpoint 1 label changed');
console.log(checkpoints.getCheckpoint('1'));
});
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
// -> 'Checkpoint 1 label changed'
// -> 'sale'
checkpoints.setCheckpoint('1', 'sold');
// -> 'Checkpoint 1 label changed'
// -> 'sold'
checkpoints.setCheckpoint('1', 'sold');
// The listener is not called when the label does not change.
checkpoints.goTo('0');
store.setCell('pets', 'fido', 'sold', false);
// -> 'Checkpoint 1 label changed'
// -> undefined
// The checkpoint no longer exists.
checkpoints.delListener(listenerId);
checkpoints.destroy();
delListener
The delListener method removes a listener that was previously added to the Checkpoints object.
delListener(listenerId: string): Checkpoints| Type | Description | |
|---|---|---|
listenerId | string | The |
| returns | Checkpoints | A reference to the |
Use the Id returned by the addCheckpointIdsListener method. Note that the Checkpoints object may re-use this Id for future listeners added to it.
Example
This example creates a Store, a Checkpoints object, registers a listener, and then removes it.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
checkpoints.delListener(listenerId);
store.setCell('pets', 'fido', 'sold', 'true');
// -> undefined
// The listener is not called.
Configuration methods
This is the collection of configuration methods within the Checkpoints interface. There is only one method, setSize.
setSize
The setSize method lets you specify how many checkpoints the Checkpoints object will store.
setSize(size: number): Checkpoints| Type | Description | |
|---|---|---|
size | number | The number of checkpoints that this |
| returns | Checkpoints | A reference to the |
If you set more checkpoints than this size, the oldest checkpoints will be pruned to make room for more recent ones.
The default size for a newly-created Checkpoints object is 100.
Example
This example creates a Store, adds a Checkpoints object, reduces the size of the Checkpoints object dramatically and then creates more than that number of checkpoints to demonstrate the oldest being pruned.
const store = createStore().setTables({pets: {fido: {views: 0}}});
const checkpoints = createCheckpoints(store);
checkpoints.setSize(2);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'views', 1);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
store.setCell('pets', 'fido', 'views', 2);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
store.setCell('pets', 'fido', 'views', 3);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['1', '2'], '3', []]
Iterator methods
This is the collection of iterator methods within the Checkpoints interface. There is only one method, forEachCheckpoint.
forEachCheckpoint
The forEachCheckpoint method takes a function that it will then call for each Checkpoint in a specified Checkpoints object.
forEachCheckpoint(checkpointCallback: CheckpointCallback): void| Type | Description | |
|---|---|---|
checkpointCallback | CheckpointCallback | The function that should be called for every Checkpoint. |
| returns | void | This has no return value. |
This method is useful for iterating over the structure of the Checkpoints object in a functional style. The checkpointCallback parameter is a CheckpointCallback function that will be called with the Id of each Checkpoint.
Example
This example iterates over each Checkpoint in a Checkpoints object.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.forEachCheckpoint((checkpointId, label) => {
console.log(`${checkpointId}:${label}`);
});
// -> '0:'
// -> '1:sale'
Lifecycle methods
This is the collection of lifecycle methods within the Checkpoints interface. There are only two lifecycle methods, clear and destroy.
clear
The clear method resets this Checkpoints object to its initial state, removing all the checkpoints it has been managing.
clear(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
Obviously this method should be used with caution as it destroys the ability to undo recent changes to the Store (though of course the Store itself is not reset by this method).
This method can be useful when a Store is being loaded via a Persister asynchronously after the Checkpoints object has been attached, and you don't want users to be able to undo the initial load of the data. In this you could call the clear method immediately after the initial load so that that is the baseline from which all subsequent changes are tracked.
If you are listening to
Example
This example creates a Store, a Checkpoints object, adds a listener, makes a change and then clears the checkpoints.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
const listenerId = checkpoints.addCheckpointIdsListener(() => {
console.log('checkpoints changed');
});
store.setCell('pets', 'fido', 'color', 'brown');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
store.setCell('pets', 'fido', 'sold', true);
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
checkpoints.clear();
// -> 'checkpoints changed'
console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
destroy
The destroy method should be called when this Checkpoints object is no longer used.
destroy(): voidThis guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.
Example
This example creates a Store, adds a Checkpoints object (that registers a CellListener with the underlying Store), and then destroys it again, removing the listener.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(store.getListenerStats().cell);
// -> 1
checkpoints.destroy();
console.log(store.getListenerStats().cell);
// -> 0
Movement methods
This is the collection of movement methods within the Checkpoints interface. There are only three movement methods, goBackward, goForward, and goTo.
goBackward
The goBackward method moves the state of the underlying Store back to the previous checkpoint, effectively performing an 'undo' on the Store data.
goBackward(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
If there is no previous checkpoint to return to, this method has no effect.
Example
This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
goForward
The goForward method moves the state of the underlying Store forwards to a future checkpoint, effectively performing an 'redo' on the Store data.
goForward(): Checkpoints| returns | Checkpoints | A reference to the |
|---|
If there is no future checkpoint to return to, this method has no effect.
Note that if you have previously used the goBackward method to undo changes, the forwards 'redo' stack will only exist while you do not make changes to the Store. In general the goForward method is expected to be used to redo changes that were just undone.
Examples
This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change. It then goes forward again to restore the state with the changes.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change. It makes a new change, the redo stack disappears, and then the attempt to forward again has no effect.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
store.setCell('pets', 'fido', 'color', 'brown');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
// The original change cannot be redone.
goTo
The goTo method moves the state of the underlying Store backwards or forwards to a specified checkpoint.
goTo(checkpointId: string): Checkpoints| Type | Description | |
|---|---|---|
checkpointId | string | The |
| returns | Checkpoints | A reference to the |
If there is no checkpoint with the Id specified, this method has no effect.
Example
This example creates a Store, a Checkpoints object, makes two changes and then goes directly to the state of the Store before the two changes. It then goes forward again one change, also using the goTo method. Finally it tries to go to a checkpoint that does not exist.
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
store.setCell('pets', 'fido', 'color', 'brown');
checkpoints.addCheckpoint('identification');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]
checkpoints.goTo('0');
console.log(store.getTables());
// -> {pets: {fido: {sold: false}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1', '2']]
checkpoints.goTo('1');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
checkpoints.goTo('3');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
Development methods
This is the collection of development methods within the Checkpoints interface. There is only one method, getListenerStats.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Checkpoints object, and is used for debugging purposes.
getListenerStats(): CheckpointsListenerStats| returns | CheckpointsListenerStats | A |
|---|
The CheckpointsListenerStats object contains a breakdown of the different types of listener.
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.
Example
This example gets the listener statistics of a Checkpoints object.
const store = createStore();
const checkpoints = createCheckpoints(store);
checkpoints.addCheckpointIdsListener(() => {
console.log('Checkpoint Ids changed');
});
checkpoints.addCheckpointListener(null, () => {
console.log('Checkpoint label changed');
});
console.log(checkpoints.getListenerStats());
// -> {checkpointIds: 1, checkpoint: 1}
Functions
There is one function, createCheckpoints, within the checkpoints module.
createCheckpoints
The createCheckpoints function creates a Checkpoints object, and is the main entry point into the checkpoints module.
createCheckpoints(store: Store): Checkpoints| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Checkpoints | A reference to the new |
A given Store can only have one Checkpoints object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Checkpoints object created by the first.
Examples
This example creates a Checkpoints object.
const store = createStore();
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
This example creates a Checkpoints object, and calls the method a second time for the same Store to return the same object.
const store = createStore();
const checkpoints1 = createCheckpoints(store);
const checkpoints2 = createCheckpoints(store);
console.log(checkpoints1 === checkpoints2);
// -> true
Type Aliases
These are the type aliases within the checkpoints module.
Listener type aliases
This is the collection of listener type aliases within the checkpoints module. There are only two listener type aliases, CheckpointIdsListener and CheckpointListener.
CheckpointIdsListener
The CheckpointIdsListener type describes a function that is used to listen to changes to the checkpoint Ids in a Checkpoints object.
(checkpoints: Checkpoints): void| Type | Description | |
|---|---|---|
checkpoints | Checkpoints | A reference to the |
| returns | void | This has no return value. |
A CheckpointIdsListener is provided when using the addCheckpointIdsListener method. See that method for specific examples.
When called, a CheckpointIdsListener is given a reference to the Checkpoints object.
CheckpointListener
The CheckpointListener type describes a function that is used to listen to changes to a checkpoint's label in a Checkpoints object.
(
checkpoints: Checkpoints,
checkpointId: Id,
): void| Type | Description | |
|---|---|---|
checkpoints | Checkpoints | A reference to the |
checkpointId | Id | The |
| returns | void | This has no return value. |
A CheckpointListener is provided when using the addCheckpointListener method. See that method for specific examples.
When called, a CheckpointListener is given a reference to the Checkpoints object, and the Id of the checkpoint whose label changed.
Callback type aliases
This is the collection of callback type aliases within the checkpoints module. There is only one type alias, CheckpointCallback.
CheckpointCallback
The CheckpointCallback type describes a function that takes a Checkpoint's Id.
(
checkpointId: Id,
label?: string,
): void| Type | Description | |
|---|---|---|
checkpointId | Id | The |
label? | string | |
| returns | void | This has no return value. |
A CheckpointCallback is provided when using the forEachCheckpoint method, so that you can do something based on every Checkpoint in the Checkpoints object. See that method for specific examples.
Identity type aliases
This is the collection of identity type aliases within the checkpoints module. There is only one type alias, CheckpointIds.
CheckpointIds
The CheckpointIds type is a representation of the list of checkpoint Ids stored in a Checkpoints object.
[Ids, Id | undefined, Ids]There are three parts to a CheckpointsIds array:
- The 'backward' checkpoint
Idsthat can be rolled backward to (in other words, the checkpoints in the undo stack for thisStore). They are in chronological order with the oldest checkpoint at the start of the array. - The current checkpoint
Idof theStore's state, orundefinedif the current state has not been checkpointed. - The 'forward' checkpoint
Idsthat can be rolled forward to (in other words, the checkpoints in the redo stack for thisStore). They are in chronological order with the newest checkpoint at the end of the array.
Development type aliases
This is the collection of development type aliases within the checkpoints module. There is only one type alias, CheckpointsListenerStats.
CheckpointsListenerStats
The CheckpointsListenerStats type describes the number of listeners registered with the Checkpoints object, and can be used for debugging purposes.
{
checkpointIds?: number;
checkpoint?: number;
}| Type | Description | |
|---|---|---|
checkpointIds? | number | The number of |
checkpoint? | number | The number of |
A CheckpointsListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.
common
The common module of the TinyBase project provides a small collection of common types used across other modules.
Functions
There is one function, defaultSorter, within the common module.
defaultSorter
The defaultSorter function is provided as a convenience to sort keys alphanumerically, and can be provided to the sliceIdSorter and rowIdSorter parameters of the setIndexDefinition method in the indexes module, for example.
defaultSorter(
sortKey1: SortKey,
sortKey2: SortKey,
): number| Type | Description | |
|---|---|---|
sortKey1 | SortKey | The first item of the pair to compare. |
sortKey2 | SortKey | The second item of the pair to compare. |
| returns | number | A number indicating how to sort the pair. |
Examples
This example creates an Indexes object.
const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []
This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names. The Slice Ids (and Row Ids within them) are alphabetically sorted using the defaultSorter function.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition(
'byFirst', // indexId
'pets', // tableId
(_, rowId) => rowId[0], // each Row's Slice Id
(_, rowId) => rowId, // each Row's sort key
defaultSorter, // sort Slice Ids
defaultSorter, // sort Row Ids by sort key
);
console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Type Aliases
These are the type aliases within the common module.
Callback type aliases
This is the collection of callback type aliases within the common module. There are only two callback type aliases, Callback and ParameterizedCallback.
Callback
The Callback type represents a function that is used as a callback and which does not take a parameter.
(): voidParameterizedCallback
The ParameterizedCallback type represents a generic function that will take an optional parameter - such as the handler of a DOM event.
(parameter?: Parameter): void| Type | Description | |
|---|---|---|
parameter? | Parameter | |
| returns | void | This has no return value. |
General type aliases
This is the collection of general type aliases within the common module. There is only one type alias, Json.
Json
The Json type is a simple alias for a string, but is used to indicate that the string should be considered to be a JSON serialization of an object.
stringIdentity type aliases
This is the collection of identity type aliases within the common module. There are only three identity type aliases, Id, IdOrNull, and Ids.
Id
The Id type is a simple alias for a string, but is used to indicate that the string should be considered to be the key of an object (such as a Row Id string used in a Table).
stringIdOrNull
The Id type is a simple alias for the union of a string or null value, where the string should be considered to be the key of an objects (such as a Row Id string used in a Table), and typically null indicates a wildcard - such as when used in the Store addRowListener method.
Id | nullIds
The Ids type is a simple alias for an array of strings, but is used to indicate that the strings should be considered to be the keys of objects (such as the Row Id strings used in a Table).
Id[]Parameter type aliases
This is the collection of parameter type aliases within the common module. There is only one type alias, SortKey.
SortKey
The SortKey type represents a value that can be used by a sort function.
string | number | booleanpersisters
The persisters module of the TinyBase project provides a simple framework for saving and loading Store data, to and from different destinations, or underlying storage types.
Several entry points are provided (in separately installed modules), each of which returns a new Persister object that can load and save a Store. Between them, these allow you to store your TinyBase data locally, remotely, to SQLite databases, and across synchronization boundaries with CRDT frameworks.
| Module | Function | Storage |
|---|---|---|
persister-browser | createSessionPersister | Browser session storage |
persister-browser | createLocalPersister | Browser local storage |
persister-indexed-db | createIndexedDbPersister | Browser IndexedDB |
persister-remote | createRemotePersister | Remote server |
persister-file | createFilePersister | Local file (where possible) |
persister-sqlite3 | createSqlite3Persister | SQLite in Node, via sqlite3 |
persister-sqlite-wasm | createSqliteWasmPersister | SQLite in a browser, via sqlite-wasm |
persister-cr-sqlite-wasm | createCrSqliteWasmPersister | SQLite CRDTs, via cr-sqlite-wasm |
persister-expo-sqlite | createExpoSqlitePersister | SQLite in React Native, via expo-sqlite |
persister-yjs | createYjsPersister | Yjs CRDTs, via yjs |
persister-automerge | createSqliteWasmPersister | Automerge CRDTs, via automerge-repo |
Since persistence requirements can be different for every app, the createCustomPersister function in this module can also be used to easily create a fully customized way to save and load Store data.
See also
Persisting Data guide
Database Persistence guide
Synchronizing Data guide
Countries demo
Todo App demos
Drawing demo
Interfaces
There is one interface, Persister, within the persisters module.
Persister
A Persister object lets you save and load Store data to and from different locations, or underlying storage types.
This is useful for preserving Store data between browser sessions or reloads, saving or loading browser state to or from a server, or saving Store data to disk in a environment with filesystem access.
Creating a Persister depends on the choice of underlying storage where the data is to be stored. Options include the createSessionPersister function, the createLocalPersister, the createRemotePersister function, and the createFilePersister function. The createCustomPersister function can also be used to easily create a fully customized way to save and load Store data.
A Persister lets you explicit save or load data, with the save method and the load method respectively. These methods are both asynchronous (since the underlying data storage may also be) and return promises. As a result you should use the await keyword to call them in a way that guarantees subsequent execution order.
When you don't want to deal with explicit persistence operations, a Persister object also provides automatic saving and loading. Automatic saving listens for changes to the Store and persists the data immediately. Automatic loading listens (or polls) for changes to the persisted data and reflects those changes in the Store.
You can start automatic saving or loading with the startAutoSave method and startAutoLoad method. Both are asynchronous since they will do an immediate save and load before starting to listen for subsequent changes. You can stop the behavior with the stopAutoSave method and stopAutoLoad method (which are synchronous).
You may often want to have both automatic saving and loading of a Store so that changes are constantly synchronized (allowing basic state preservation between browser tabs, for example). The framework has some basic provisions to prevent race conditions - for example it will not attempt to save data if it is currently loading it and vice-versa - and will sequentially schedule methods that could cause race conditions.
That said, be aware that you should always comprehensively test your persistence strategy to understand the opportunity for data loss (in the case of trying to save data to a server under poor network conditions, for example).
To help debug such issues, since v4.0.4, the create methods for all Persister objects take an optional onIgnoredError argument. This is a handler for the errors that the Persister would otherwise ignore when trying to save or load data (such as when handling corrupted stored data). It's recommended you use this for debugging persistence issues, but only in a development environment. Database-based Persister objects also take an optional onSqlCommand argument for logging commands and queries made to the underlying database.
Examples
This example creates a Store, persists it to the browser's session storage as a JSON string, changes the persisted data, updates the Store from it, and finally destroys the Persister again.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load();
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
This example creates a Store, and automatically saves and loads it to the browser's session storage as a JSON string. Changes to the Store data, or the persisted data (implicitly firing a StorageEvent), are reflected accordingly.
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"felix":{"species":"cat"}}},{}]'
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
Getter methods
This is the collection of getter methods within the Persister interface. There is only one method, getStore.
getStore
The getStore method returns a reference to the underlying Store that is backing this Persister object.
getStore(): StoreExample
This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.
const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();
persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
sessionStorage.clear();
Lifecycle methods
This is the collection of lifecycle methods within the Persister interface. There are only two lifecycle methods, destroy and schedule.
destroy
The destroy method should be called when this Persister object is no longer used.
destroy(): Persister| returns | Persister |
|---|
This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession.
Example
This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(store.getListenerStats().transaction);
// -> 1
persister.destroy();
console.log(store.getListenerStats().transaction);
// -> 0
schedule
The schedule method allows you to queue up a series of asynchronous actions that must run in sequence during persistence.
schedule(actions: Promise<any>[]): Promise<Persister>For example, a database Persister may need to ensure that multiple asynchronous tasks to check and update the database schema are completed before data is written to it. Therefore it's most likely you will be using this method inside your setPersisted implementation.
Call this method to add a single asynchronous action, or a sequence of them in one call. This will also start to run the first task in the queue (which once complete will then run the next, and so on), and so this method itself is also asynchronous and returns a promise of the Persister.
Example
This example creates a custom Persister object against a newly-created Store and then sequences two tasks in order to update its data on a hypothetical remote system.
const store = createStore();
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return await getDataFromRemoteSystem();
},
async (getContent) => {
// setPersisted
await persister.schedule(
async () => await checkRemoteSystemIsReady(),
async () => await sendDataToRemoteSystem(getContent()),
);
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
Since
v4.0.0
Load methods
This is the collection of load methods within the Persister interface. There are only three load methods, load, startAutoLoad, and stopAutoLoad.
load
The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.
load(
initialTables?: Tables,
initialValues?: Values,
): Promise<Persister>| Type | Description | |
|---|---|---|
initialTables? | Tables | An optional |
initialValues? | Values | An optional |
| returns | Promise<Persister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial Tables object for the Store will be if there is nothing currently persisted. Using this instead of the initialTables parameter in the regular createStore function allows you to easily instantiate a Store whether it's loading from previously persisted storage or being run for the first time.
This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Examples
This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.
sessionStorage.setItem('pets', '[{"pets":{"fido":{"species":"dog"}}},{}]');
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.clear();
This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
sessionStorage.clear();
startAutoLoad
The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.
startAutoLoad(
initialTables?: Tables,
initialValues?: Values,
): Promise<Persister>| Type | Description | |
|---|---|---|
initialTables? | Tables | An optional |
initialValues? | Values | An optional |
| returns | Promise<Persister> | A Promise containing a reference to the |
The optional parameter allows you to specify what the initial Tables object for the Store will be if there is nothing at first persisted. Using this instead of the initialTables parameter in the regular createStore function allows you to easily instantiate a Store whether it's loading from previously persisted storage or being run for the first time.
This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.
This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.destroy();
sessionStorage.clear();
stopAutoLoad
The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.
stopAutoLoad(): PersisterIf the Persister is not currently set to automatically load, this method has no effect.
Example
This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();
// In another browser tab:
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
persister.stopAutoLoad();
// In another browser tab:
sessionStorage.setItem(
'pets',
'[{"pets":{"felix":{"species":"cat"}}},{}]',
);
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.
persister.destroy();
sessionStorage.clear();
Save methods
This is the collection of save methods within the Persister interface. There are only three save methods, save, startAutoSave, and stopAutoSave.
save
The save method takes data from the Store with which the Persister is associated and persists it into storage, once.
save(): Promise<Persister>This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
startAutoSave
The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.
startAutoSave(): Promise<Persister>This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.
This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
sessionStorage.clear();
stopAutoSave
The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.
stopAutoSave(): PersisterIf the Persister is not currently set to automatically save, this method has no effect.
Example
This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();
store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
persister.stopAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"toto":{"species":"dog"}}},{}]'
// Store change has not been automatically saved.
sessionStorage.clear();
Development methods
This is the collection of development methods within the Persister interface. There is only one method, getStats.
getStats
The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.
getStats(): PersisterStats| returns | PersisterStats | A |
|---|
The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.
The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.
Example
This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.
const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();
store.setTables({pets: {felix: {species: 'cat'}}});
// ...
sessionStorage.setItem('pets', '[{"pets":{"toto":{"species":"dog"}}},{}]');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(persister.getStats());
// -> {loads: 2, saves: 2}
persister.destroy();
sessionStorage.clear();
Functions
There is one function, createCustomPersister, within the persisters module.
createCustomPersister
The createCustomPersister function creates a Persister object that you can configure to persist the Store in any way you wish.
createCustomPersister<ListeningHandle>(
store: Store,
getPersisted: () => Promise<undefined | [Tables, Values]>,
setPersisted: (getContent: () => [Tables, Values], getTransactionChanges?: GetTransactionChanges) => Promise<void>,
addPersisterListener: (listener: PersisterListener) => ListeningHandle,
delPersisterListener: (listeningHandle: ListeningHandle) => void,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
getPersisted | () => Promise<undefined | [Tables, Values]> | An asynchronous function which will fetch content from the persistence layer (or |
setPersisted | (getContent: () => [Tables, Values], getTransactionChanges?: GetTransactionChanges) => Promise<void> | An asynchronous function which will send content to the persistence layer. Since v4.0, it receives functions for getting the |
addPersisterListener | (listener: PersisterListener) => ListeningHandle | A function that will register a |
delPersisterListener | (listeningHandle: ListeningHandle) => void | A function that will unregister the listener from the underlying changes to the persistence layer. It receives whatever was returned from your |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide functions that handle how to fetch, write, and listen to, the persistence layer.
The other creation functions (such as the createSessionPersister function and createFilePersister function, for example) all use this function under the covers. See those implementations for ideas on how to implement your own Persister types.
This API changed in v4.0. Any custom persisters created on previous versions should be upgraded. Most notably, the setPersisted function parameter is provided with a getContent function to get the content from the Store itself, rather than being passed pre-serialized JSON. It also receives information about the changes made during a transaction. The getPersisted function must return the content (or nothing) rather than JSON. startListeningToPersisted has been renamed addPersisterListener, and stopListeningToPersisted has been renamed delPersisterListener.
Example
This example creates a custom Persister object and persists the Store to a local string called storeJson and which would automatically load by polling for changes every second.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
let storeJson;
const persister = createCustomPersister(
store,
async () => {
// getPersisted
return JSON.parse(storeJson);
},
async (getContent) => {
// setPersisted
storeJson = JSON.stringify(getContent());
},
(listener) => setInterval(listener, 1000),
(interval) => clearInterval(interval),
);
await persister.save();
console.log(storeJson);
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
storeJson = '[{"pets":{"fido":{"species":"dog","color":"brown"}}},{}]';
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
persister.destroy();
Type Aliases
These are the type aliases within the persisters module.
Configuration type aliases
This is the collection of configuration type aliases within the persisters module. There are 6 configuration type aliases in total.
DatabasePersisterConfig
The DatabasePersisterConfig type describes the configuration of a database-oriented Persister, such as those for SQLite.
DpcJson | DpcTabularThere are two modes for persisting a Store with a database:
- A JSON serialization of the whole
Store, which is stored in a single row of a table (normally calledtinybase) within the database. This is configured by providing aDpcJsonobject. - A tabular mapping of
TableIdsto database table names (and vice-versa).Valuesare stored in a separate special table (normally calledtinybase_values). This is configured by providing aDpcTabularobject.
Please see the DpcJson and DpcTabular type documentation for more detail on each. If not specified otherwise, JSON serialization will be used for persistence.
Changes made to the database (outside of this Persister) are picked up immediately if they are made via the same connection or library that it is using. If the database is being changed by another client, the Persister needs to poll for changes. Hence both configuration types also contain an autoLoadIntervalSeconds property which indicates how often it should do that. This defaults to 1 second.
Note that all the nested types within this type have a 'Dpc' prefix, short for 'DatabasePersisterConfig'.
Examples
When applied to a database Persister, this DatabasePersisterConfig will load and save a JSON serialization from and to a table called my_tinybase, polling the database every 2 seconds. See DpcJson for more details on these settings.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'json',
storeTableName: 'my_tinybase',
autoLoadIntervalSeconds: 2,
};
When applied to a database Persister, this DatabasePersisterConfig will load and save tabular data from and to tables specified in the load and save mappings. See DpcTabular for more details on these settings.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
};
Since
v4.0.0
DpcJson
The DpcJson type describes the configuration of a database-oriented Persister operating in serialized JSON mode.
{
mode: "json";
storeTableName?: string;
autoLoadIntervalSeconds?: number;
}| Type | Description | |
|---|---|---|
mode | "json" | The mode to be used for persisting the |
storeTableName? | string | An optional string which indicates the name of a table in the database which will be used to serialize the |
autoLoadIntervalSeconds? | number | How often the |
The only setting is the storeTableName property, which indicates the name of a table in the database which will be used to serialize the Store content into. It defaults to tinybase.
That table in the database will be given two columns: a primary key column called _id, and one called store. The Persister will place a single row in this table with _ in the _id column, and the JSON serialization in the store column, something like:
> SELECT * FROM tinybase;
+-----+-----------------------------------------------------+
| _id | store |
+-----+-----------------------------------------------------+
| _ | [{"pets":{"fido":{"species":"dog"}}},{"open":true}] |
+-----+-----------------------------------------------------+
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load and save a JSON serialization from and to a table called tinybase_json.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'json',
storeTableName: 'tinybase_json',
};
Since
v4.0.0
DpcTabular
The DpcTabular type describes the configuration of a database-oriented Persister that is operating in tabular mapping mode.
{
mode: "tabular";
tables?: {
load?: DpcTabularLoad;
save?: DpcTabularSave;
};
values?: DpcTabularValues;
autoLoadIntervalSeconds?: number;
}| Type | Description | |
|---|---|---|
mode | "tabular" | The mode to be used for persisting the |
tables? | { load?: DpcTabularLoad; save?: DpcTabularSave; } | The settings for how the |
values? | DpcTabularValues | The settings for how the |
autoLoadIntervalSeconds? | number | How often the |
It is important to note that both the tabular mapping in ('save') and out ('load') of an underlying database are disabled by default. This is to ensure that if you pass in an existing populated database you don't run the immediate risk of corrupting or losing all your data.
This configuration therefore takes a tables property object (with child load and save property objects) and a values property object. These indicate how you want to load and save Tables and Values respectively. At least one of these two properties are required for the Persister to do anything!
Note that if you are planning to both load from and save to a database, it is important to make sure that the load and save table mappings are symmetrical. For example:
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
};
See the documentation for the DpcTabularLoad, DpcTabularSave, and DpcTabularValues types for more details on how to configure the tabular mapping mode.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load and save Tables data from and to tables specified in the load and save mappings, and Values data from and to a table called my_tinybase_values.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {petsInDb: 'pets', speciesInDb: 'species'},
save: {pets: 'petsInDb', species: 'speciesInDb'},
},
values: {
load: true,
save: true,
tableName: 'my_tinybase_values',
},
};
Since
v4.0.0
DpcTabularLoad
The DpcTabularLoad type describes the configuration for loading Tables in a database-oriented Persister that is operating in tabular mode.
{[tableName: string]: {
tableId: Id;
rowIdColumnName?: string;
} | Id}It is an object where each key is a name of a database table, and the value is a child configuration object for how that table should be loaded into the Store. The properties of the child configuration object are:
| Type | Description | |
|---|---|---|
tableId | Id | The Id of the Store Table into which data from this database table should be loaded. |
rowIdColumnName? | string | The optional name of the column in the database table that will be used as the Row Ids in the Store Table, defaulting to '_id'. |
As a shortcut, if you do not need to specify a custom rowIdColumnName, you can simply provide the Id of the Store Table instead of the whole object.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load the data of two database tables (called 'petsInDb' and 'speciesInDb') into two Store Tables (called 'pets' and 'species'). One has a column for the Row Id called 'id' and the other defaults it to '_id'.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
load: {
petsInDb: {tableId: 'pets', rowIdColumnName: 'id'},
speciesInDb: 'species',
},
},
};
Imagine database tables that look like this:
> SELECT * FROM petsInDb;
+-------+---------+-------+
| id | species | color |
+-------+---------+-------+
| fido | dog | brown |
| felix | cat | black |
+-------+---------+-------+
> SELECT * FROM speciesInDb;
+------+-------+
| _id | price |
+------+-------+
| dog | 5 |
| cat | 4 |
+------+-------+
With the configuration above, this will load into a Store with Tables that look like this:
{
"pets": {
"fido": {"species": "dog", "color": "brown"},
"felix": {"species": "cat", "color": "black"},
},
"species": {
"dog": {"price": 5},
"cat": {"price": 4},
},
}
Since
v4.0.0
DpcTabularSave
The DpcTabularSave type describes the configuration for saving Tables in a database-oriented Persister that is operating in tabular mode.
{[tableId: Id]: {
tableName: string;
rowIdColumnName?: string;
deleteEmptyColumns?: boolean;
deleteEmptyTable?: boolean;
} | string}It is an object where each key is an Id of a Store Table, and the value is a child configuration object for how that Table should be saved out to the database. The properties of the child configuration object are:
| Type | Description | |
|---|---|---|
tableName | string | The name of the database table out to which the Store Table should be saved. |
rowIdColumnName? | string | The optional name of the column in the database table that will be used to save the Row Ids from the Store Table, defaulting to '_id'. |
deleteEmptyColumns? | boolean | Whether columns in the database table will be removed if they are empty in the Store Table, defaulting to false. |
deleteEmptyTable? | boolean | Whether tables in the database will be removed if the Store Table is empty, defaulting to false. |
As a shortcut, if you do not need to specify a custom rowIdColumnName, or enable the deleteEmptyColumns or deleteEmptyTable settings, you can simply provide the name of the database table instead of the whole object.
deleteEmptyColumns and deleteEmptyTable only have a guaranteed effect when an explicit call is made to the Persister's save method. Columns and tables will not necessarily be removed when the Persister is incrementally 'autoSaving', due to performance reasons. If you want to be sure that your database table matches a TinyBase Table without any extraneous columns, simply call the save method at an idle moment.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will save the data of two Store Tables (called 'pets' and 'species') into two database tables (called 'petsInDb' and 'speciesInDb'). One has a column for the Row Id called 'id' and will delete columns and the whole table if empty, the other defaults to '_id' and will not delete columns or the whole table if empty.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
tables: {
save: {
pets: {
tableName: 'petsInDb',
deleteEmptyColumns: true,
deleteEmptyTable: true,
},
species: 'speciesInDb',
},
},
};
Imagine a Store with Tables that look like this:
{
"pets": {
"fido": {"species": "dog", "color": "brown"},
"felix": {"species": "cat", "color": "black"},
},
"species": {
"dog": {"price": 5},
"cat": {"price": 4},
},
}
With the configuration above, this will save out to a database with tables that look like this:
> SELECT * FROM petsInDb;
+-------+---------+-------+
| id | species | color |
+-------+---------+-------+
| fido | dog | brown |
| felix | cat | black |
+-------+---------+-------+
> SELECT * FROM speciesInDb;
+------+-------+
| _id | price |
+------+-------+
| dog | 5 |
| cat | 4 |
+------+-------+
Since
v4.0.0
DpcTabularValues
The DpcTabularValues type describes the configuration for handling Values in a database-oriented Persister that is operating in tabular mode.
{
load?: boolean;
save?: boolean;
tableName?: string;
}| Type | Description | |
|---|---|---|
load? | boolean | |
save? | boolean | |
tableName? | string | The optional name of the database table from and to which the |
Note that both loading and saving of Values from and to the database are disabled by default.
The 'Dpc' prefix indicates that this type is used within the DatabasePersisterConfig type.
Example
When applied to a database Persister, this DatabasePersisterConfig will load and save the data of a Store's Values into a database table called 'my_tinybase_values'.
const databasePersisterConfig: DatabasePersisterConfig = {
mode: 'tabular',
values: {
load: true,
save: true,
tableName: 'my_tinybase_values',
},
};
Since
v4.0.0
Creation type aliases
This is the collection of creation type aliases within the persisters module. There is only one type alias, PersisterListener.
PersisterListener
A PersisterListener is a callback that lets a Persister inform the Store that a change has happened to the underlying data.
(
getContent?: () => [Tables, Values],
getTransactionChanges?: GetTransactionChanges,
): void| Type | Description | |
|---|---|---|
getContent? | () => [Tables, Values] | An optional function that, if provided, returns an array of |
getTransactionChanges? | GetTransactionChanges | An optional function that, if provided, returns a |
| returns | void | This has no return value. |
If the listener has the getTransactionChanges parameter, it will be used to make an incremental change to the Store. If not, but the getContent function is available, that will be used to make a wholesale change to the Store. If neither are present, the content will be loaded from the Persister's load method.
Since
v4.0.0
Development type aliases
This is the collection of development type aliases within the persisters module. There is only one type alias, PersisterStats.
PersisterStats
The PersisterStats type describes the number of times a Persister object has loaded or saved data.
{
loads?: number;
saves?: number;
}| Type | Description | |
|---|---|---|
loads? | number | The number of times data has been loaded. |
saves? | number | The number of times data has been saved. |
A PersisterStats object is returned from the getStats method, and is only populated in a debug build.
persister-automerge
The persister-automerge module of the TinyBase project provides a way to save and load Store data, to and from an Automerge document.
A single entry point, the createAutomergePersister function, is provided, which returns a new Persister object that can bind a Store to a provided Automerge document handle (and in turn, its document).
See also
Synchronizing Data guide
Since
v4.0.0
createAutomergePersister
The createAutomergePersister function creates a Persister object that can persist the Store to an Automerge document.
createAutomergePersister(
store: Store,
docHandle: DocHandle<any>,
docMapName?: string,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
docHandle | DocHandle<any> | The Automerge document handler to persist the |
docMapName? | string | The name of the map used inside the Automerge document to sync the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide the Automerge document handler to persist it with.
Examples
This example creates a Persister object and persists the Store to an Automerge document.
const docHandler = new AutomergeRepo.Repo({network: []}).create();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createAutomergePersister(store, docHandler);
await persister.save();
// Store will be saved to the document.
console.log(docHandler.doc);
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
persister.destroy();
This more complex example uses Automerge networking to keep two Store objects (each with their own Persister objects and Automerge documents) in sync with each other using a network.
// Bind the first Store to a network-enabled automerge-repo
const repo1 = new AutomergeRepo.Repo({
network: [new BroadcastChannelNetworkAdapter()],
});
const docHandler1 = repo1.create();
await docHandler1.value();
const store1 = createStore();
const persister1 = createAutomergePersister(store1, docHandler1);
await persister1.startAutoLoad();
await persister1.startAutoSave();
// Bind the second Store to a different network-enabled automerge-repo
const repo2 = new AutomergeRepo.Repo({
network: [new BroadcastChannelNetworkAdapter()],
});
const docHandler2 = repo2.find(docHandler1.documentId);
await docHandler2.value();
const store2 = createStore();
const persister2 = createAutomergePersister(store2, docHandler2);
await persister2.startAutoLoad();
await persister2.startAutoSave();
// A function that waits briefly and then for the documents to synchronize
// with each other, merely for the purposes of sequentiality in this example.
const syncDocsWait = async () => {
await new Promise((resolve) => setTimeout(() => resolve(0), 10));
await docHandler1.value();
await docHandler2.value();
};
// Wait for the documents to synchronize in their initial state.
await syncDocsWait();
// Make a change to each of the two Stores.
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setValues({open: true});
// Wait for the documents to synchronize in their new state.
await syncDocsWait();
// Ensure the Stores are in sync.
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
console.log(store2.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
persister1.destroy();
persister2.destroy();
Since
v4.0.0
persister-browser
The persister-browser module of the TinyBase project lets you save and load Store data to and from browser storage.
Two entry points are provided, each of which returns a new Persister object that can load and save a Store:
- The
createSessionPersisterfunction returns aPersisterthat uses the browser's session storage. - The
createLocalPersisterfunction returns aPersisterthat uses the browser's local storage.
See also
Persisting Data guide
createLocalPersister
The createLocalPersister function creates a Persister object that can persist the Store to the browser's local storage.
createLocalPersister(
store: Store,
storageName: string,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a Persister object and persists the Store to the browser's local storage.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');
await persister.save();
console.log(localStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
localStorage.clear();
createSessionPersister
The createSessionPersister function creates a Persister object that can persist the Store to the browser's session storage.
createSessionPersister(
store: Store,
storageName: string,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
storageName | string | The unique key to identify the storage location. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.
Example
This example creates a Persister object and persists the Store to the browser's session storage.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '[{"pets":{"fido":{"species":"dog"}}},{}]'
persister.destroy();
sessionStorage.clear();
persister-cr-sqlite-wasm
The persister-cr-sqlite-wasm module of the TinyBase project lets you save and load Store data to and from a local CR-SQLite database (in an appropriate environment).
See also
Persisting Data guide
Since
v4.0.0
createCrSqliteWasmPersister
The createCrSqliteWasmPersister function creates a Persister object that can persist the Store to a local CR-SQLite database (in an appropriate environment).
createCrSqliteWasmPersister(
store: Store,
db: DB,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, args?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
db | DB | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, args?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a Persister object and persists the Store to a local CR-SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(await db.execO('SELECT * FROM my_tinybase;'));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a Persister object and persists the Store to a local SQLite database with tabular mapping.
const crSqlite3 = await initWasm();
const db = await crSqlite3.open();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createCrSqliteWasmPersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(await db.execO('SELECT * FROM pets;'));
// -> [{_id: 'fido', species: 'dog'}]
await db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.0
persister-expo-sqlite
The persister-expo-sqlite module of the TinyBase project lets you save and load Store data to and from a local Expo-SQLite database (in an appropriate React Native environment).
Note that this Persister is currently experimental as Expo themselves iterate on the underlying database library API.
See also
Persisting Data guide
Since
v4.0.3
createExpoSqlitePersister
The createExpoSqlitePersister function creates a Persister object that can persist the Store to a local Expo-SQLite database (in an appropriate React Native environment).
createExpoSqlitePersister(
store: Store,
db: SQLiteDatabase,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, args?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
db | SQLiteDatabase | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, args?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
Note that this Persister is currently experimental as Expo themselves iterate on the underlying database library API.
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a Persister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
const db = SQLite.openDatabase('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM my_tinybase;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
db.all(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a Persister object and persists the Store to a local SQLite database with tabular mapping.
const db = SQLite.openDatabase('my.db');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createExpoSqlitePersister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM pets;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
db.all(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.3
persister-file
The persister-file module of the TinyBase project lets you save and load Store data to and from a local file system (in an appropriate environment).
See also
Persisting Data guide
createFilePersister
The createFilePersister function creates a Persister object that can persist the Store to a local file (in an appropriate environment).
createFilePersister(
store: Store,
filePath: string,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
filePath | string | The location of the local file to persist the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a filePath parameter which identifies the file to persist it to.
Example
This example creates a Persister object and persists the Store to a local file.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createFilePersister(store, '/app/persisted.json');
await persister.save();
// Store JSON will be saved to the file.
await persister.load();
// Store JSON will be loaded from the file.
persister.destroy();
persister-indexed-db
The persister-indexed-db module of the TinyBase project lets you save and load Store data to and from browser IndexedDB storage.
See also
Persisting Data guide
createIndexedDbPersister
The createIndexedDbPersister function creates a Persister object that can persist the Store to the browser's IndexedDB storage.
createIndexedDbPersister(
store: Store,
dbName: string,
autoLoadIntervalSeconds?: number,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
dbName | string | The unique key to identify the IndexedDB to use. |
autoLoadIntervalSeconds? | number | How often to poll the database when in 'autoLoad' mode, defaulting to 1. |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a dbName parameter which is unique to your application. This is the key used to identify which IndexedDB to use.
Within that database, this Persister will create two object stores: one called 't', and one called 'v'. These will contain the Store's tabular and key-value data respectively, using 'k' and 'v' to store the key and value of each entry, as shown in the example.
Note that it is not possible to reactively detect changes to a browser's IndexedDB. If you do choose to enable automatic loading for the Persister (with the startAutoLoad method), it needs to poll the database for changes. The autoLoadIntervalSeconds method is used to indicate how often to do this.
Example
This example creates a Persister object and persists the Store to the browser's IndexedDB storage.
const store =
createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}})
.setValues({open: true});
const persister = createIndexedDbPersister(store, 'petStore');
await persister.save();
// IndexedDB ->
// database petStore:
// objectStore t:
// object 0:
// k: "pets"
// v: {fido: {species: dog}}
// object 1:
// k: "species"
// v: {dog: {price: 5}}
// objectStore v:
// object 0:
// k: "open"
// v: true
persister.destroy();
Since
v4.2.0
persister-partykit-client
The persister-partykit-client module of the TinyBase project contains the client portion of the PartyKit integration.
It contains a Persister which, when run in a PartyKit client environment, lets you save and load Store data from the client to durable PartyKit cloud storage of a server (that is using the complementary persister-partykit-server module).
This enables synchronization of the same Store across multiple clients in a PartyKit party room.
Note that both the client and server parts of this Persister are currently experimental as PartyKit is new and still maturing.
See also
Persisting Data guide
Since
4.3.0
Functions
There is one function, createPartyKitPersister, within the persister-partykit-client module.
createPartyKitPersister
The createPartyKitPersister function creates a Persister object that can persist the Store to durable PartyKit storage, enabling synchronization of the same Store across multiple clients.
createPartyKitPersister(
store: Store,
connection: PartySocket,
configOrStoreProtocol?: PartyKitPersisterConfig | "http" | "https",
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
connection | PartySocket | The PartySocket to use for participating in the PartyKit room. |
configOrStoreProtocol? | PartyKitPersisterConfig | "http" | "https" | The |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a connection parameter which is a PartyKit PartySocket that you have already instantiated with details of the host and room.
All suitably-equipped TinyBase clients connecting to that room will get to share synchronized Store state.
The server room's Store is considered the source of truth. If it is a newly-created room, then calling the save method on this Persister will initiate it. If, however, there is already a Store present on the server, the save method will fail gracefully.
In general, you are strongly recommended to use the auto-save and auto-load functionality to stay in sync incrementally with the server, as per the example below. This pattern will handle newly-created servers and newly-created clients - and the synchronization involved in joining rooms, leaving them, or temporarily going offline.
See the PartyKit client socket API documentation for more details.
Example
This example creates a Persister object and persists the Store to the browser's IndexedDB storage.
const store =
createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}})
.setValues({open: true});
const partySocket = new PartySocket({host: PARTYKIT_HOST, room: 'my_room'});
const persister = createPartyKitPersister(store, partySocket);
await persister.startAutoLoad();
await persister.startAutoSave();
// Store will now be synchronized with the room.
persister.destroy();
Since
4.3.0
Type Aliases
There is one type alias, PartyKitPersisterConfig, within the persister-partykit-client module.
PartyKitPersisterConfig
The PartyKitPersisterConfig type describes the configuration of a PartyKit Persister on the client side.
{
storeProtocol?: "http" | "https";
storePath?: string;
}| Type | Description | |
|---|---|---|
storeProtocol? | "http" | "https" | The HTTP protocol to use (in addition to the websocket channel). This defaults to 'https' but you may wish to use 'http' for local PartyKit development. |
storePath? | string | The path used to set and get the whole |
The defaults (if used on both the server and client) will work fine, but if you are building more complex PartyKit apps and you need to configure path names, for example, then this is the thing to use.
Example
When applied to a PartyKit Persister, this PartyKitPersisterConfig will load and save a JSON serialization from and to an end point in your room called /my_tinybase, and use HTTP (rather than the default HTTPS) as the protocol.
Note that this would require you to also add the matching storePath setting to the TinyBasePartyKitServerConfig on the server side.
const partyKitPersisterConfig: PartyKitPersisterConfig = {
storeProtocol: 'http',
storePath: '/my_tinybase',
};
Since
v4.3.9
persister-partykit-server
The persister-partykit-server module of the TinyBase project contains the server portion of the PartyKit integration.
It contains a class which, when run in a PartyKit server environment, lets you save and load Store data from a client (that is using the complementary persister-partykit-client module) to durable PartyKit cloud storage.
This enables synchronization of the same Store across multiple clients in a PartyKit party room.
Note that both the client and server parts of this Persister are currently experimental as PartyKit is new and still maturing.
See also
Persisting Data guide
Since
4.3.0
Classes
There is one class, TinyBasePartyKitServer, within the persister-partykit-server module.
TinyBasePartyKitServer
A TinyBasePartyKitServer is the server component for persisting the Store to durable PartyKit storage, enabling synchronization of the same Store across multiple clients.
This extends the PartyKit Server class, which provides a selection of methods you are expected to implement. The TinyBasePartyKitServer implements only two of them, the onMessage method and the onRequest method, as well as the constructor.
If you wish to use TinyBasePartyKitServer as a general PartyKit server, you can implement other methods. But you must remember to call the super implementations of those methods to ensure the TinyBase synchronization stays supported in addition to your own custom functionality. The same applies to the constructor if you choose to implement that.
// This is your PartyKit server entry point.
export default class extends TinyBasePartyServer {
constructor(party) {
super(party);
// custom constructor code
}
async onStart() {
// no need to call super.onStart()
console.log('Server started');
}
async onMessage(message, client) {
await super.onMessage(message, client);
// custom onMessage code
}
async onRequest(request) {
// custom onRequest code, else:
return await super.onRequest(request);
}
}
See the PartyKit server API documentation for more details.
Constructors
There is one constructor, constructor, within the TinyBasePartyKitServer class.
constructor
Methods
These are the methods within the TinyBasePartyKitServer class.
onMessage
The onMessage method is called when the server receives a message from a client.
onMessage(
message: string,
client: Connection,
): void| Type | Description | |
|---|---|---|
message | string | |
client | Connection | |
| returns | void | This has no return value. |
If you choose to implement additional functionality in this method, you must remember to call the super implementation to ensure the TinyBase synchronization stays supported:
export default class extends TinyBasePartyServer {
async onMessage(message, client) {
await super.onMessage(message, client);
// custom onMessage code
}
}
See the PartyKit server API documentation for more details.
onRequest
The onRequest method is called when a HTTP request is made to the party URL.
onRequest(request: Request): Promise<Response>| Type | Description | |
|---|---|---|
request | Request | |
| returns | Promise<Response> |
If you choose to implement additional functionality in this method, you must remember to call the super implementation to ensure the TinyBase synchronization stays supported:
export default class extends TinyBasePartyServer {
async onRequest(request) {
// custom onRequest code, else:
return await super.onRequest(request);
}
}
See the PartyKit server API documentation for more details.
Properties
There is one property, config, within the TinyBasePartyKitServer class.
config
The config property is used to optionally configure the server, using an object of the TinyBasePartyKitServerConfig type.
See the documentation for that type for more details.
Type Aliases
There is one type alias, TinyBasePartyKitServerConfig, within the persister-partykit-server module.
TinyBasePartyKitServerConfig
The TinyBasePartyKitServerConfig type describes the configuration of a PartyKit Persister on the server side.
{
storePath?: string;
storagePrefix?: string;
responseHeaders?: HeadersInit;
}| Type | Description | |
|---|---|---|
storePath? | string | The path used to set and get the whole |
storagePrefix? | string | The prefix used before all the keys in the server's durable storage. Use this in case you are worried about the |
responseHeaders? | HeadersInit | An object containing the extra HTTP(S) headers returned to the client from this server. This defaults to the following three headers to allow CORS: If you set this field, it will override the default completely. So, for example, if you add another header but still want the CORS defaults, you will need to explicitly set the Access-Control-Allow headers above again. |
The defaults (if used on both the server and client) will work fine, but if you are building more complex PartyKit apps and you need to configure path names, for example, then this is the type to use.
Example
When set as the config in a TinyBasePartyKitServer, this TinyBasePartyKitServerConfig will expect clients to load and save their JSON serialization from and to an end point in the room called /my_tinybase. Note that this would require you to also add the matching storePath setting to the PartyKitPersisterConfig on the client side.
It will also store the data in the durable storage with a prefix of 'tinybase_' in case you are worried about colliding with other data stored in the room.
export default class extends TinyBasePartyServer {
readonly config: TinyBasePartyKitServerConfig = {
storePath: '/my_tinybase',
storagePrefix: 'tinybase_',
};
};
Since
v4.3.9
persister-remote
The persister-remote module of the TinyBase project lets you save and load Store data to and from a remote server.
See also
Persisting Data guide
createRemotePersister
The createRemotePersister function creates a Persister object that can persist the Store to a remote server.
createRemotePersister(
store: Store,
loadUrl: string,
saveUrl: string,
autoLoadIntervalSeconds?: number,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
loadUrl | string | The endpoint that supports a |
saveUrl | string | The endpoint that supports a |
autoLoadIntervalSeconds? | number | How often to poll the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide loadUrl and saveUrl parameters. These identify the endpoints of the server that support the GET method (to fetch the Store JSON to load) and the POST method (to send the Store JSON to save) respectively.
For when you choose to enable automatic loading for the Persister (with the startAutoLoad method), it will poll the loadUrl for changes. The autoLoadIntervalSeconds method is used to indicate how often to do this.
Example
This example creates a Persister object and persists the Store to a remote server.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createRemotePersister(
store,
'https://example.com/load',
'https://example.com/save',
5,
);
await persister.save();
// Store JSON will be sent to server in a POST request.
await persister.load();
// Store JSON will be fetched from server with a GET request.
persister.destroy();
persister-sqlite-wasm
The persister-sqlite-wasm module of the TinyBase project lets you save and load Store data to and from a local SQLite database (in an appropriate environment).
See also
Persisting Data guide
Since
v4.0.0
createSqliteWasmPersister
The createSqliteWasmPersister function creates a Persister object that can persist the Store to a local SQLite database (in an appropriate environment).
createSqliteWasmPersister(
store: Store,
sqlite3: any,
db: any,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, args?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
sqlite3 | any | The WASM module that was returned from |
db | any | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, args?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide sqlite3 and db parameters which identify the WASM module and database instance respectively.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The fourth argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the fourth argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a Persister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(
store,
sqlite3,
db,
'my_tinybase',
);
await persister.save();
// Store will be saved to the database.
console.log(db.exec('SELECT * FROM my_tinybase;', {rowMode: 'object'}));
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
db.exec(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a Persister object and persists the Store to a local SQLite database with tabular mapping.
const sqlite3 = await sqlite3InitModule();
const db = new sqlite3.oo1.DB(':memory:', 'c');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqliteWasmPersister(store, sqlite3, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(db.exec('SELECT * FROM pets;', {rowMode: 'object'}));
// -> [{_id: 'fido', species: 'dog'}]
db.exec(`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.0
persister-sqlite3
The persister-sqlite3 module of the TinyBase project lets you save and load Store data to and from a local SQLite database (in an appropriate environment).
See also
Persisting Data guide
Since
v4.0.0
createSqlite3Persister
The createSqlite3Persister function creates a Persister object that can persist the Store to a local SQLite database (in an appropriate environment).
createSqlite3Persister(
store: Store,
db: Database,
configOrStoreTableName?: string | DatabasePersisterConfig,
onSqlCommand?: (sql: string, args?: any[]) => void,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
db | Database | The database instance that was returned from |
configOrStoreTableName? | string | DatabasePersisterConfig | A |
onSqlCommand? | (sql: string, args?: any[]) => void | An optional handler called every time the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide a db parameter which identifies the database instance.
A database Persister uses one of two modes: either a JSON serialization of the whole Store stored in a single row of a table (the default), or a tabular mapping of Table Ids to database table names and vice-versa).
The third argument is a DatabasePersisterConfig object that configures which of those modes to use, and settings for each. If the third argument is simply a string, it is used as the storeTableName property of the JSON serialization.
See the documentation for the DpcJson and DpcTabular types for more information on how both of those modes can be configured.
Examples
This example creates a Persister object and persists the Store to a local SQLite database as a JSON serialization into the my_tinybase table. It makes a change to the database directly and then reloads it back into the Store.
const db = new sqlite3.Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, 'my_tinybase');
await persister.save();
// Store will be saved to the database.
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM my_tinybase;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
await new Promise((resolve) =>
db.all(
'UPDATE my_tinybase SET store = ' +
`'[{"pets":{"felix":{"species":"cat"}}},{}]' WHERE _id = '_';`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
persister.destroy();
This example creates a Persister object and persists the Store to a local SQLite database with tabular mapping.
const db = new sqlite3.Database(':memory:');
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSqlite3Persister(store, db, {
mode: 'tabular',
tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
});
await persister.save();
console.log(
await new Promise((resolve) =>
db.all('SELECT * FROM pets;', (_, rows) => resolve(rows)),
),
);
// -> [{_id: 'fido', species: 'dog'}]
await new Promise((resolve) =>
db.all(
`INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
resolve,
),
);
await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
persister.destroy();
Since
v4.0.0
persister-yjs
The persister-yjs module of the TinyBase project provides a way to save and load Store data, to and from a Yjs document.
A single entry point, the createYjsPersister function, is provided, which returns a new Persister object that can bind a Store to a provided Yjs document.
See also
Synchronizing Data guide
Since
v4.0.0
createYjsPersister
The createYjsPersister function creates a Persister object that can persist the Store to a Yjs document.
createYjsPersister(
store: Store,
yDoc: Doc,
yMapName?: string,
onIgnoredError?: (error: any) => void,
): Persister| Type | Description | |
|---|---|---|
store | Store | The |
yDoc | Doc | The Yjs document to persist the |
yMapName? | string | The name of the Y.Map used inside the Yjs document to sync the |
onIgnoredError? | (error: any) => void | An optional handler for the errors that the |
| returns | Persister | A reference to the new |
As well as providing a reference to the Store to persist, you must provide the Yjs document to persist it to.
Examples
This example creates a Persister object and persists the Store to a Yjs document.
const doc = new Y.Doc();
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createYjsPersister(store, doc);
await persister.save();
// Store will be saved to the document.
console.log(doc.toJSON());
// -> {tinybase: {t: {pets: {fido: {species: 'dog'}}}, v: {}}}
persister.destroy();
This more complex example uses Yjs updates to keep two Store objects (each with their own Persister objects and Yjs documents) in sync with each other. We use the await keyword extensively for the purpose of ensuring sequentiality in this example.
Typically, real-world synchronization would happen between two systems via a Yjs connection provider. Here, we synthesize that with the syncDocs function.
const doc1 = new Y.Doc();
const doc2 = new Y.Doc();
// A function to manually synchronize documents with each other. Typically
// this would happen over the wire, via a Yjs connection provider.
const syncDocs = async () => {
Y.applyUpdate(doc1, Y.encodeStateAsUpdate(doc2));
Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1));
};
// Bind a persisted Store to each document.
const store1 = createStore();
const persister1 = createYjsPersister(store1, doc1);
await persister1.startAutoLoad();
await persister1.startAutoSave();
const store2 = createStore();
const persister2 = createYjsPersister(store2, doc2);
await persister2.startAutoLoad();
await persister2.startAutoSave();
// Synchronize the documents in their initial state.
await syncDocs();
// Make a change to each of the two Stores.
store1.setTables({pets: {fido: {species: 'dog'}}});
store2.setValues({open: true});
// ...
// Synchronize the documents with each other again.
await syncDocs();
// Ensure the Stores are in sync.
console.log(store1.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
console.log(store2.getContent());
// -> [{pets: {fido: {species: 'dog'}}}, {open: true}]
persister1.destroy();
persister2.destroy();
Since
v4.0.0
tools
The tools module of the TinyBase project provides utilities for working with TinyBase during development.
This module is not intended to be directly used at runtime in a production environment.
Since
v2.2.0
Interfaces
There is one interface, Tools, within the tools module.
Tools
A Tools object lets you run various utilities on, and get certain information about, Store objects in development.
See also
Developer Tools guides
Since
v2.2.0
Getter methods
This is the collection of getter methods within the Tools interface. There is only one method, getStore.
getStore
The getStore method returns a reference to the underlying Store that is backing this Tools object.
getStore(): StoreExample
This example creates a Tools object against a newly-created Store and then gets its reference in order to update its data.
const tools = createTools(createStore());
tools.getStore().setCell('species', 'dog', 'price', 5);
console.log(tools.getStoreStats().totalCells);
// -> 1
Since
v3.0.0
Modelling methods
This is the collection of modelling methods within the Tools interface. There are 4 modelling methods in total.
getPrettyStoreApi
The getPrettyStoreApi method attempts to return prettified code-generated .d.ts and .ts(x) files that describe the schema of a Store and React bindings (since v3.1) in an ORM style.
getPrettyStoreApi(storeName: string): Promise<[string, string, string, string]>| Type | Description | |
|---|---|---|
storeName | string | The name you want to provide to the generated |
| returns | Promise<[string, string, string, string]> | A set of four strings representing the contents of the |
This is simply a wrapper around the getStoreApi method that attempts to invoke the prettier module (which it hopes you have installed) to format the generated code. If prettier is not present, the output will resemble that of the underlying getStoreApi method.
The method is asynchronous, so you should use the await keyword or handle the results as a promise.
The method takes a single argument which represents the name you want the generated store object to have in code. You are expected to save the four files yourself, as, respectively:
[storeName].d.ts[storeName].ts[storeName]-ui-react.d.ts[storeName]-ui-react.tsx
Also you should save these alongside each other so that the .ts(x) files can import types from the .d.ts files.
See the documentation for the getStoreApi method for details of the content of the generated files.
Examples
This example creates a Tools object and generates code for a Store that already has a TablesSchema.
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
const tools = createTools(store);
const [dTs, ts, uiReactDTs, uiReactTsx] = await createTools(
store,
).getPrettyStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[17]);
// -> `export type Tables = {pets?: {[rowId: Id]: {price?: number}}};`
const tsLines = ts.split('\n');
console.log(tsLines[84]);
// -> ' hasPetsTable: (): boolean => store.hasTable(PETS),'
This example creates a Tools object and generates code for a Store that doesn't already have a TablesSchema.
const store = createStore().setTable('pets', {
fido: {price: 5},
felix: {price: 4},
});
const tools = createTools(store);
const [dTs, ts, uiReactDTs, uiReactTsx] = await createTools(
store,
).getPrettyStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[17]);
// -> 'export type Tables = {pets?: {[rowId: Id]: {price: number}}};'
const tsLines = ts.split('\n');
console.log(tsLines[86]);
// -> ' hasPetsTable: (): boolean => store.hasTable(PETS),'
Since
v2.2.0
getStoreApi
The getStoreApi method returns code-generated .d.ts and .ts(x) files that describe the schema of a Store and React bindings (since v3.1) in an ORM style.
getStoreApi(storeName: string): [string, string, string, string]| Type | Description | |
|---|---|---|
storeName | string | The name you want to provide to the generated |
| returns | [string, string, string, string] | A set of four strings representing the contents of the |
If the Store does not already have an explicit TablesSchema or ValuesSchema associated with it, the data in the Store will be scanned to attempt to infer new schemas. The method returns four strings (which should be saved as files) though if no schema can be inferred, the strings will be empty.
The method takes a single argument which represents the name you want the generated store object to have in code. You are expected to save the four files yourself, as, respectively:
[storeName].d.ts[storeName].ts[storeName]-ui-react.d.ts[storeName]-ui-react.tsx
Also you should save these alongside each other so that the .ts(x) files can import types from the .d.ts files.
The .d.ts and .ts(x) files that are generated are designed to resemble the main TinyBase Store and React binding files, but provide named types and methods that describe the domain of the schema in the Store.
For example, from a Store that has a pets Table, you will get methods like getPetsTable, types like PetsRow, and hooks and components that are more specific versions of the underlying getTable method or the Row type, and so on. For example:
Store type | Equivalent generated type |
|---|---|
Table | [Table]Table |
Row | [Table]Row |
(Cell) Id | [Table]CellId |
CellCallback | [Table]CellCallback |
| ... | ... |
Store method | Equivalent generated method |
|---|---|
setTable | set[Table]Table |
hasRow | has[Table]Row |
getCell | get[Table][Cell]Cell |
| ... | ... |
Equivalent to the TinyBase createStore function, a create[StoreName] function will also be created. This acts as the main entry point to the generated implementation.
Each method is refined correctly to take, or return, the types specified by the schema. For example, if the pets Table has a numeric price Cell in the schema, the getPetsPriceCell method will be typed to return a number.
The tables above include just a sample of the generated output. For the full set of methods and types generated by this method, inspect the output directly.
Examples
This example creates a Tools object and generates code for a Store that already has a TablesSchema.
const store = createStore().setTablesSchema({
pets: {
price: {type: 'number'},
},
});
const [dTs, ts, uiReactDTs, uiReactTsx] =
createTools(store).getStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[3]);
// -> `export type Tables = {'pets'?: {[rowId: Id]: {'price'?: number}}};`
const tsLines = ts.split('\n');
console.log(tsLines[39]);
// -> 'getPetsTable: (): PetsTable => store.getTable(PETS) as PetsTable,'
This example creates a Tools object and generates code for a Store that doesn't already have a TablesSchema.
const store = createStore().setTable('pets', {
fido: {price: 5},
felix: {price: 4},
});
const [dTs, ts, uiReactDTs, uiReactTsx] =
createTools(store).getStoreApi('shop');
const dTsLines = dTs.split('\n');
console.log(dTsLines[3]);
// -> `export type Tables = {'pets'?: {[rowId: Id]: {'price': number}}};`
const tsLines = ts.split('\n');
console.log(tsLines[41]);
// -> 'getPetsTable: (): PetsTable => store.getTable(PETS) as PetsTable,'
Since
v2.2.0
getStoreTablesSchema
The getStoreTablesSchema method returns the TablesSchema of the Store as an object.
getStoreTablesSchema(): TablesSchema| returns | TablesSchema | A |
|---|
If the Store does not already have an explicit TablesSchema associated with it, the data in the Store will be scanned to attempt to infer a new TablesSchema.
To be successful, this requires all the values of a given Cell across a Table object's Row objects to have a consistent type. If a given Cell Id appears in every Row, then a default for that Cell is specified in the TablesSchema, based on the most common value found.
Examples
This example creates a Tools object and gets the schema of a Store that already has a TablesSchema.
const store = createStore().setTablesSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
species: {
price: {type: 'number'},
},
});
const schema = createTools(store).getStoreTablesSchema();
console.log(schema.pets);
// -> {species: {type: 'string'}, color: {type: 'string'}}
This example creates a Tools object and infers the schema of a Store that doesn't already have a TablesSchema.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5, barks: true},
cat: {price: 4, purrs: true},
});
const schema = createTools(store).getStoreTablesSchema();
console.log(schema.pets.species);
// -> {type: 'string', default: 'dog'}
console.log(schema.pets.color);
// -> {type: 'string', default: 'black'}
console.log(schema.species.price);
// -> {type: 'number', default: 5}
console.log(schema.species.barks);
// -> {type: 'boolean'}
console.log(schema.species.purrs);
// -> {type: 'boolean'}
Since
v3.0.0
getStoreValuesSchema
The getStoreValuesSchema method returns the ValuesSchema of the Store as an object.
getStoreValuesSchema(): ValuesSchema| returns | ValuesSchema | A |
|---|
If the Store does not already have an explicit ValuesSchema associated with it, the data in the Store will be scanned to infer a new ValuesSchema, based on the types of the Values present. Note that, unlike the inference of Cell values in the TablesSchema, it is not able to determine whether a Value should have a default or not.
Examples
This example creates a Tools object and gets the schema of a Store that already has a ValuesSchema.
const store = createStore().setValuesSchema({
open: {type: 'boolean', default: true},
employees: {type: 'number'},
});
const schema = createTools(store).getStoreValuesSchema();
console.log(schema);
// -> {open: {type: 'boolean', default: true}, employees: {type: 'number'}}
This example creates a Tools object and infers the schema of a Store that doesn't already have a ValuesSchema.
const store = createStore().setValues({open: true, employees: 3});
const schema = createTools(store).getStoreValuesSchema();
console.log(schema);
// -> {open: {type: 'boolean'}, employees: {type: 'number'}}
Since
v3.0.0
Statistics methods
This is the collection of statistics methods within the Tools interface. There is only one method, getStoreStats.
getStoreStats
The getStoreStats method provides a set of statistics about the Store, and is used for debugging purposes.
getStoreStats(detail?: boolean): StoreStats| Type | Description | |
|---|---|---|
detail? | boolean | An optional boolean that indicates more detailed stats about the inner structure of the |
| returns | StoreStats | A |
Examples
This example creates a Tools object and gets basic statistics about it.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
})
.setValues({open: true, employees: 3});
const tools = createTools(store);
console.log(tools.getStoreStats());
// -> {totalTables: 2, totalRows: 5, totalCells: 8, totalValues: 2, jsonLength: 212}
This example creates a Tools object and gets detailed statistics about it.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
});
const stats = createTools(store).getStoreStats(true);
console.log(stats.totalTables);
// -> 2
console.log(stats.totalRows);
// -> 5
console.log(stats.totalCells);
// -> 8
console.log(stats.detail.tables.pets.tableRows);
// -> 3
console.log(stats.detail.tables.pets.tableCells);
// -> 6
console.log(stats.detail.tables.pets.rows);
// -> {fido: {rowCells: 2}, felix: {rowCells: 2}, cujo: {rowCells: 2}}
Since
v2.2.0
Functions
There is one function, createTools, within the tools module.
createTools
The createTools function creates a Tools object, and is the main entry point into the tools module.
createTools(store: Store): Tools| Type | Description | |
|---|---|---|
store | Store | The |
| returns | Tools | A reference to the new |
A given Store can only have one Tools object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Tools object created by the first.
Examples
This example creates a Tools object.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
})
.setValues({open: true, employees: 3});
const tools = createTools(store);
console.log(tools.getStoreStats());
// -> {totalTables: 2, totalRows: 5, totalCells: 8, totalValues: 2, jsonLength: 212}
This example creates a Tools object, and calls the method a second time for the same Store to return the same object.
const store = createStore();
const tools1 = createTools(store);
const tools2 = createTools(store);
console.log(tools1 === tools2);
// -> true
Since
v2.2.0
Type Aliases
These are the type aliases within the tools module.
StoreStats
The StoreStats type describes a set of statistics about the Store, and is used for debugging purposes.
{
totalTables: number;
totalRows: number;
totalCells: number;
totalValues: number;
jsonLength: number;
detail?: StoreStatsDetail;
}| Type | Description | |
|---|---|---|
totalTables | number | |
totalRows | number | The number of |
totalCells | number | The number of |
totalValues | number | |
jsonLength | number | The string length of the |
detail? | StoreStatsDetail | Additional detailed statistics about the |
A StoreStats object is returned from the getStoreStats method.
Since
v2.2.0
StoreStatsDetail
The StoreStatsDetail type describes a more detailed set of statistics about the Store, and is used for debugging purposes.
{tables: {[tableId: Id]: StoreStatsTableDetail}}| Type | Description | |
|---|---|---|
tables | {[tableId: Id]: StoreStatsTableDetail} |
A StoreStatsDetail object is added to the StoreStats object (returned from the getStoreStats method) when the detail flag is specified.
Since
v2.2.0
StoreStatsRowDetail
The StoreStatsRowDetail type describes statistics about a single Row in the Store, and is used for debugging purposes.
{rowCells: number}Since
v2.2.0
StoreStatsTableDetail
The StoreStatsTableDetail type describes a detailed set of statistics about a single Table in the Store, and is used for debugging purposes.
{
tableRows: number;
tableCells: number;
rows: {[rowId: Id]: StoreStatsRowDetail};
}| Type | Description | |
|---|---|---|
tableRows | number | |
tableCells | number | The number of |
rows | {[rowId: Id]: StoreStatsRowDetail} | Detail about the |
Since
v2.2.0
ui-react
The ui-react module of the TinyBase project provides both hooks and components to make it easy to create reactive apps with Store objects.
The hooks in this module provide access to the data and structures exposed by other modules in the project. As well as immediate access, they all register listeners such that components using those hooks are selectively re-rendered when data changes.
The components in this module provide a further abstraction over those hooks to ease the composition of user interfaces that use TinyBase.
See also
Building UIs guides
Building UIs With Metrics guide
Building UIs With Indexes guide
Building UIs With Relationships guide
Building UIs With Queries guide
Building UIs With Checkpoints guide
Countries demo
Todo App demos
Drawing demo
Functions
These are the functions within the ui-react module.
Checkpoints hooks
This is the collection of checkpoints hooks within the ui-react module. There are 14 checkpoints hooks in total.
useCheckpoint
The useCheckpoint hook returns the label for a checkpoint, and registers a listener so that any changes to that result will cause a re-render.
useCheckpoint(
checkpointId: string,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): string | undefined| Type | Description | |
|---|---|---|
checkpointId | string | The |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | string | undefined | A string label for the requested checkpoint, an empty string if it was never set, or |
A Provider component is used to wrap part of an application in a context, and it can contain a default Checkpoints object or a set of Checkpoints objects named by Id. The useCheckpoint hook lets you indicate which Checkpoints object to get data for: omit the optional final parameter for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide a Checkpoints object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the label will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Checkpoints object outside the application, which is used in the useCheckpoint hook by reference. A change to the checkpoint label re-renders the component.
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => <span>{useCheckpoint('1', checkpoints)}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span></span>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>sale</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpoint hook.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCheckpoint('0')}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>initial</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpoint hook.
const App = ({checkpoints}) => (
<Provider checkpointsById={{petCheckpoints: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCheckpoint('0', 'petCheckpoints')}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>initial</span>'
useCheckpointIds
The useCheckpointIds hook returns an array of the checkpoint Ids being managed by this Checkpoints object, and registers a listener so that any changes to that result will cause a re-render.
useCheckpointIds(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): CheckpointIds| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | CheckpointIds | A |
A Provider component is used to wrap part of an application in a context, and it can contain a default Checkpoints object or a set of Checkpoints objects named by Id. The useCheckpointIds hook lets you indicate which Checkpoints object to get data for: omit the optional parameter for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide a Checkpoints object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the checkpoint Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Checkpoints object outside the application, which is used in the useCheckpointIds hook by reference. A change to the checkpoint Ids re-renders the component.
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span>{JSON.stringify(useCheckpointIds(checkpoints))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<span>[["0"],null,[]]</span>'
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>[["0"],"1",[]]</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointIds hook.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useCheckpointIds())}</span>;
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointIds hook.
const App = ({checkpoints}) => (
<Provider checkpointsById={{petCheckpoints: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCheckpointIds('petCheckpoints'))}</span>
);
const checkpoints = createCheckpoints(
createStore().setTable('pets', {fido: {species: 'dog'}}),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
useCheckpointIdsListener
The useCheckpointIdsListener hook registers a listener function with the Checkpoints object that will be called whenever its set of checkpoints changes.
useCheckpointIdsListener(
listener: CheckpointIdsListener,
listenerDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void| Type | Description | |
|---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpointIds hook).
Unlike the addCheckpointIdsListener method, which returns a listener Id and requires you to remove it manually, the useCheckpointIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Checkpoints object will be deleted.
Example
This example uses the useCheckpointIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => {
useCheckpointIdsListener(() => console.log('Checkpoint Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 1
store.setCell('pets', 'fido', 'sold', true);
// -> 'Checkpoint Ids changed'
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
root.unmount();
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 0
useCheckpointListener
The useCheckpointListener hook registers a listener function with the Checkpoints object that will be called whenever the label of a checkpoint changes.
useCheckpointListener(
checkpointId: IdOrNull,
listener: CheckpointListener,
listenerDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void| Type | Description | |
|---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpoint hook).
You can either listen to a single checkpoint label (by specifying the checkpoint Id as the method's first parameter), or changes to any checkpoint label (by providing a null wildcard).
Unlike the addCheckpointListener method, which returns a listener Id and requires you to remove it manually, the useCheckpointListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Checkpoints object will be deleted.
Example
This example uses the useCheckpointListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => {
useCheckpointListener('0', () =>
console.log('Checkpoint label changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(checkpoints.getListenerStats().checkpoint);
// -> 1
checkpoints.setCheckpoint('0', 'initial');
// -> 'Checkpoint label changed'
root.unmount();
console.log(checkpoints.getListenerStats().checkpoint);
// -> 0
useCheckpoints
The useCheckpoints hook is used to get a reference to a Checkpoints object from within a Provider component context.
useCheckpoints(id?: string): Checkpoints | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Checkpoints | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Checkpoints object (or a set of Checkpoints objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useCheckpoints hook lets you either get a reference to the default Checkpoints object (when called without a parameter), or one of the Checkpoints objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Checkpoint object is provided. A component within it then uses the useCheckpoints hook to get a reference to the Checkpoints object again, without the need to have it passed as a prop.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCheckpoints().getListenerStats().checkpointIds}</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Checkpoints object is provided, named by Id. A component within it then uses the useCheckpoints hook with that Id to get a reference to the Checkpoints object again, without the need to have it passed as a prop.
const App = ({checkpoints}) => (
<Provider checkpointsById={{petStore: checkpoints}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{useCheckpoints('petStore').getListenerStats().checkpointIds}
</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
useCheckpointsIds
The useCheckpointsIds hook is used to retrieve the Ids of all the named Checkpoints objects present in the current Provider component context.
useCheckpointsIds(): IdsExample
This example adds two named Checkpoints objects to a Provider context and an inner component accesses their Ids.
const App = ({store}) => {
const store1 = useCreateStore(createStore);
const checkpoints1 = useCreateCheckpoints(store1, createCheckpoints);
const store2 = useCreateStore(createStore);
const checkpoints2 = useCreateCheckpoints(store2, createCheckpoints);
return (
<Provider checkpointsById={{checkpoints1, checkpoints2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useCheckpointsIds())}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["checkpoints1","checkpoints2"]</span>'
Since
v4.1.0
useCheckpointsOrCheckpointsById
The useCheckpointsOrCheckpointsById hook is used to get a reference to a Checkpoints object from within a Provider component context, or have it passed directly to this hook.
useCheckpointsOrCheckpointsById(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Checkpoints | undefined| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | Either an |
| returns | Checkpoints | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Checkpoints object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Checkpoints-based components).
This is unlikely to be used often. For most situations, you will want to use the useCheckpoints hook.
Example
This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointsOrCheckpointsById hook to get a reference to the Checkpoints object again, without the need to have it passed as a prop. Note however, that unlike the useCheckpoints hook example, this component would also work if you were to pass the Checkpoints object directly into it, making it more portable.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = ({checkpoints}) => (
<span>
{JSON.stringify(
useCheckpointsOrCheckpointsById(checkpoints).getCheckpointIds(),
)}
</span>
);
const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
Since
v4.1.0
useCreateCheckpoints
The useCreateCheckpoints hook is used to create a Checkpoints object within a React application with convenient memoization.
useCreateCheckpoints(
store: Store,
create: (store: Store) => Checkpoints,
createDeps?: DependencyList,
): Checkpoints| Type | Description | |
|---|---|---|
store | Store | A reference to the |
create | (store: Store) => Checkpoints | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Checkpoints | A reference to the |
It is possible to create a Checkpoints object outside of the React app with the regular createCheckpoints function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Checkpoints object being created every time the app renders or re-renders, the useCreateCheckpoints hook wraps the creation in a memoization.
The useCreateCheckpoints hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.
If your create function contains other dependencies, the changing of which should also cause the Checkpoints object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Checkpoints object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Checkpoints object at the top level of a React application. Even though the App component is rendered twice, the Checkpoints object creation only occurs once by default.
const App = () => {
const store = useCreateStore(createStore);
const checkpoints = useCreateCheckpoints(store, (store) => {
console.log('Checkpoints created');
return createCheckpoints(store).setSize(10);
});
return <span>{JSON.stringify(checkpoints.getCheckpointIds())}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Checkpoints created'
root.render(<App />);
// No second Checkpoints creation
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
This example creates a Checkpoints object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateCheckpoints hook takes the size prop as a dependency, and so the Checkpoints object is created again on the second render.
const App = ({size}) => {
const store = useCreateStore(createStore);
const checkpoints = useCreateCheckpoints(
store,
(store) => {
console.log(`Checkpoints created, size ${size}`);
return createCheckpoints(store).setSize(size);
},
[size],
);
return <span>{JSON.stringify(checkpoints.getCheckpointIds())}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App size={20} />);
// -> 'Checkpoints created, size 20'
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
root.render(<App size={50} />);
// -> 'Checkpoints created, size 50'
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
useGoBackwardCallback
The useGoBackwardCallback hook returns a callback that moves the state of the underlying Store back to the previous checkpoint, effectively performing an 'undo' on the Store data.
useGoBackwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will go backward to the previous checkpoint - such as when clicking an undo button.
If there is no previous checkpoint to return to, this callback has no effect.
Example
This example uses the useGoBackwardCallback hook to create an event handler which goes backward in the checkpoint stack when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span id="span" onClick={useGoBackwardCallback(checkpoints)}>
Backward
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
useGoForwardCallback
The useGoForwardCallback hook returns a callback that moves the state of the underlying Store forwards to a future checkpoint, effectively performing an 'redo' on the Store data.
useGoForwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will go forward to the next checkpoint - such as when clicking an redo button.
If there is no future checkpoint to return to, this callback has no effect.
Example
This example uses the useGoForwardCallback hook to create an event handler which goes backward in the checkpoint stack when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
<span id="span" onClick={useGoForwardCallback(checkpoints)}>
Forward
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
useGoToCallback
The useGoToCallback hook returns a parameterized callback that can be used to move the state of the underlying Store backwards or forwards to a specified checkpoint.
useGoToCallback<Parameter>(
getCheckpointId: (parameter: Parameter) => string,
getCheckpointIdDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
then?: (checkpoints: Checkpoints, checkpointId: string) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getCheckpointId | (parameter: Parameter) => string | A function which returns an |
getCheckpointIdDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpoints: Checkpoints, checkpointId: string) => void | A function which is called after the checkpoint is moved, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. This parameter defaults to an empty array. |
This hook is useful, for example, when creating an event handler that will move the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint Id to move to.
The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the checkpoint has been set.
The Checkpoints object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useGoToCallback hook to create an event handler which moves to a checkpoint when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const handleClick = useGoToCallback(() => '0', [], checkpoints);
return (
<span id="span" onClick={handleClick}>
Goto 0
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
useRedoInformation
The useRedoInformation hook returns an UndoOrRedoInformation array that indicates if and how you can move the state of the underlying Store forwards to a future checkpoint.
useRedoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | UndoOrRedoInformation |
|
This hook is useful if you are building an redo button: the information contains whether a redo action is available (to enable the button), the callback to perform the redo action, the checkpoint Id that will be redone, and its label, if available.
Example
This example uses the useUndoInformation hook to create a redo button.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const [canRedo, handleRedo, id, label] = useRedoInformation(checkpoints);
return canRedo ? (
<span onClick={handleRedo}>Redo {label}</span>
) : (
<span>Nothing to redo</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>Nothing to redo</span>'
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
checkpoints.goTo('0');
console.log(app.innerHTML);
// -> '<span>Redo color</span>'
useSetCheckpointCallback
The useSetCheckpointCallback hook returns a parameterized callback that can be used to record a checkpoint of a Store into a Checkpoints object that can be reverted to in the future.
useSetCheckpointCallback<Parameter>(
getCheckpoint?: (parameter: Parameter) => string,
getCheckpointDeps?: DependencyList,
checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
then?: (checkpointId: string, checkpoints: Checkpoints, label?: string) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getCheckpoint? | (parameter: Parameter) => string | An optional function which returns a string that will be used to describe the actions leading up to this checkpoint, based on the parameter the callback will receive (and which is most likely a DOM event). |
getCheckpointDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpointId: string, checkpoints: Checkpoints, label?: string) => void | A function which is called after the checkpoint is set, with the new checkpoint |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will set the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint label.
The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the checkpoint has been set.
The Checkpoints object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetCheckpointCallback hook to create an event handler which sets a checkpoint when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const handleClick = useSetCheckpointCallback(
(e) => `with #${e.target.id} button`,
[],
checkpoints,
(checkpointId, checkpoints, label) =>
console.log(`Checkpoint ${checkpointId} set, ${label}`),
);
return (
<span id="span" onClick={handleClick}>
Set
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
store.setCell('pets', 'nemo', 'color', 'orange');
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Checkpoint 1 set, with #span button'
useUndoInformation
The useUndoInformation hook returns an UndoOrRedoInformation array that indicates if and how you can move the state of the underlying Store backward to the previous checkpoint.
useUndoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation| Type | Description | |
|---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
| returns | UndoOrRedoInformation |
|
This hook is useful if you are building an undo button: the information contains whether an undo action is available (to enable the button), the callback to perform the undo action, the current checkpoint Id that will be undone, and its label, if available.
Example
This example uses the useUndoInformation hook to create an undo button.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
const [canUndo, handleUndo, id, label] = useUndoInformation(checkpoints);
return canUndo ? (
<span onClick={handleUndo}>Undo {label}</span>
) : (
<span>Nothing to undo</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>Nothing to undo</span>'
store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
console.log(app.innerHTML);
// -> '<span>Undo color</span>'
Indexes hooks
This is the collection of indexes hooks within the ui-react module. There are 9 indexes hooks in total.
useSliceRowIds
The useSliceRowIds hook gets the list of Row Ids in a given Slice, and registers a listener so that any changes to that result will cause a re-render.
useSliceRowIds(
indexId: string,
sliceId: string,
indexesOrIndexesId?: IndexesOrIndexesId,
): Ids| Type | Description | |
|---|---|---|
indexId | string | |
sliceId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useSliceRowIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide an Indexes object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row Ids in the Slice will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates an Indexes object outside the application, which is used in the useSliceRowIds hook by reference. A change to the Row Ids in the Slice re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<span>
{JSON.stringify(useSliceRowIds('bySpecies', 'dog', indexes))}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceRowIds hook.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useSliceRowIds('bySpecies', 'dog'))}</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceRowIds hook.
const App = ({indexes}) => (
<Provider indexesById={{petIndexes: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useSliceRowIds('bySpecies', 'dog', 'petIndexes'))}
</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
useSliceRowIdsListener
The useSliceRowIdsListener hook registers a listener function with the Indexes object that will be called whenever the Row Ids in a Slice change.
useSliceRowIdsListener(
indexId: IdOrNull,
sliceId: IdOrNull,
listener: SliceRowIdsListener,
listenerDeps?: DependencyList,
indexesOrIndexesId?: IndexesOrIndexesId,
): void| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceRowIds hook).
You can either listen to a single Slice (by specifying the Index Id and Slice Id as the method's first two parameters), or changes to any Slice (by providing null wildcards).
Both, either, or neither of the indexId and sliceId parameters can be wildcarded with null. You can listen to a specific Slice in a specific Index, any Slice in a specific Index, a specific Slice in any Index, or any Slice in any Index.
Unlike the addSliceRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useSliceRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useSliceRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes object.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => {
useSliceRowIdsListener('bySpecies', 'dog', () =>
console.log('Slice Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App indexes={indexes} />);
console.log(indexes.getListenerStats().sliceRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Slice Row Ids changed'
root.unmount();
console.log(indexes.getListenerStats().sliceRowIds);
// -> 0
useCreateIndexes
The useCreateIndexes hook is used to create an Indexes object within a React application with convenient memoization.
useCreateIndexes(
store: Store,
create: (store: Store) => Indexes,
createDeps?: DependencyList,
): Indexes| Type | Description | |
|---|---|---|
store | Store | A reference to the |
create | (store: Store) => Indexes | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Indexes | A reference to the |
It is possible to create an Indexes object outside of the React app with the regular createIndexes function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Indexes object being created every time the app renders or re-renders, the useCreateIndexes hook wraps the creation in a memoization.
The useCreateIndexes hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.
If your create function contains other dependencies, the changing of which should also cause the Indexes object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Indexes object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates an Indexes object at the top level of a React application. Even though the App component is rendered twice, the Indexes object creation only occurs once by default.
const App = () => {
const store = useCreateStore((store) =>
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
);
const indexes = useCreateIndexes(store, (store) => {
console.log('Indexes created');
return createIndexes(store).setIndexDefinition(
'bySpecies',
'pets',
'species',
);
});
return <span>{JSON.stringify(indexes.getSliceIds('bySpecies'))}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Indexes created'
root.render(<App />);
// No second Indexes creation
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
This example creates an Indexes object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateIndexes hook takes the cellToIndex prop as a dependency, and so the Indexes object is created again on the second render.
const App = ({cellToIndex}) => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
}),
);
const indexes = useCreateIndexes(
store,
(store) => {
console.log(`Index created for ${cellToIndex} cell`);
return createIndexes(store).setIndexDefinition(
'byCell',
'pets',
cellToIndex,
);
},
[cellToIndex],
);
return <span>{JSON.stringify(indexes.getSliceIds('byCell'))}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App cellToIndex="species" />);
// -> 'Index created for species cell'
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
root.render(<App cellToIndex="color" />);
// -> 'Index created for color cell'
console.log(app.innerHTML);
// -> '<span>["brown","black"]</span>'
useIndexIds
The useIndexIds hook gets an array of the Index Ids registered with an Indexes object, and registers a listener so that any changes to that result will cause a re-render.
useIndexIds(indexesOrIndexesId?: IndexesOrIndexesId): Ids| Type | Description | |
|---|---|---|
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useIndexIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide an Indexes object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Index Ids in the Indexes object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Indexes object outside the application, which is used in the useIndexIds hook by reference. A newly-registered Index re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
const App = () => <span>{JSON.stringify(useIndexIds(indexes))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(app.innerHTML);
// -> '<span>["bySpecies"]</span>'
Since
v4.1.0
useIndexes
The useIndexes hook is used to get a reference to an Indexes object from within a Provider component context.
useIndexes(id?: string): Indexes | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Indexes | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Indexes object (or a set of Indexes objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useIndexes hook lets you either get a reference to the default Indexes object (when called without a parameter), or one of the Indexes objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useIndexes hook to get a reference to the Indexes object again, without the need to have it passed as a prop.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => <span>{useIndexes().getListenerStats().sliceIds}</span>;
const indexes = createIndexes(createStore());
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which an Indexes object is provided, named by Id. A component within it then uses the useIndexes hook with that Id to get a reference to the Indexes object again, without the need to have it passed as a prop.
const App = ({indexes}) => (
<Provider indexesById={{petStore: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useIndexes('petStore').getListenerStats().sliceIds}</span>
);
const indexes = createIndexes(createStore());
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
useIndexesIds
The useIndexesIds hook is used to retrieve the Ids of all the named Indexes objects present in the current Provider component context.
useIndexesIds(): IdsExample
This example adds two named Indexes objects to a Provider context and an inner component accesses their Ids.
const App = ({store}) => {
const store1 = useCreateStore(createStore);
const indexes1 = useCreateIndexes(store1, createIndexes);
const store2 = useCreateStore(createStore);
const indexes2 = useCreateIndexes(store2, createIndexes);
return (
<Provider indexesById={{indexes1, indexes2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useIndexesIds())}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["indexes1","indexes2"]</span>'
Since
v4.1.0
useIndexesOrIndexesById
The useIndexesOrIndexesById hook is used to get a reference to an Indexes object from within a Provider component context, or have it passed directly to this hook.
useIndexesOrIndexesById(indexesOrIndexesId?: IndexesOrIndexesId): Indexes | undefined| Type | Description | |
|---|---|---|
indexesOrIndexesId? | IndexesOrIndexesId | Either an |
| returns | Indexes | undefined | A reference to the |
This is mostly of use when you are developing a component that needs an Indexes object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Indexes-based components).
This hook is unlikely to be used often. For most situations, you will want to use the useIndexes hook.
Example
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useIndexesOrIndexesById hook to get a reference to the Indexes object again, without the need to have it passed as a prop. Note however, that unlike the useIndexes hook example, this component would also work if you were to pass the Indexes object directly into it, making it more portable.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = ({indexes}) => (
<span>
{JSON.stringify(useIndexesOrIndexesById(indexes).getIndexIds())}
</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["bySpecies"]</span>'
Since
v4.1.0
useSliceIds
The useSliceIds hook gets the list of Slice Ids in an Index, and registers a listener so that any changes to that result will cause a re-render.
useSliceIds(
indexId: string,
indexesOrIndexesId?: IndexesOrIndexesId,
): Ids| Type | Description | |
|---|---|---|
indexId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useSliceIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide a Indexes object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Slice Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates an Indexes object outside the application, which is used in the useSliceIds hook by reference. A change to the Slice Ids re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<span>{JSON.stringify(useSliceIds('bySpecies', indexes))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<span>["dog","cat","worm"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceIds hook.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSliceIds('bySpecies'))}</span>;
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceIds hook.
const App = ({indexes}) => (
<Provider indexesById={{petIndexes: indexes}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useSliceIds('bySpecies', 'petIndexes'))}</span>
);
const indexes = createIndexes(
createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
}),
).setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
useSliceIdsListener
The useSliceIdsListener hook registers a listener function with the Indexes object that will be called whenever the Slice Ids in an Index change.
useSliceIdsListener(
indexId: IdOrNull,
listener: SliceIdsListener,
listenerDeps?: DependencyList,
indexesOrIndexesId?: IndexesOrIndexesId,
): void| Type | Description | |
|---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceIds hook).
You can either listen to a single Index (by specifying the Index Id as the method's first parameter), or changes to any Index (by providing a null wildcard).
Unlike the addSliceIdsListener method, which returns a listener Id and requires you to remove it manually, the useSliceIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useSliceIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes object.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => {
useSliceIdsListener('bySpecies', () => console.log('Slice Ids changed'));
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App indexes={indexes} />);
console.log(indexes.getListenerStats().sliceIds);
// -> 1
store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids changed'
root.unmount();
console.log(indexes.getListenerStats().sliceIds);
// -> 0
Metrics hooks
This is the collection of metrics hooks within the ui-react module. There are 7 metrics hooks in total.
useCreateMetrics
The useCreateMetrics hook is used to create a Metrics object within a React application with convenient memoization.
useCreateMetrics(
store: Store,
create: (store: Store) => Metrics,
createDeps?: DependencyList,
): Metrics| Type | Description | |
|---|---|---|
store | Store | A reference to the |
create | (store: Store) => Metrics | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Metrics | A reference to the |
It is possible to create a Metrics object outside of the React app with the regular createMetrics function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Metrics object being created every time the app renders or re-renders, the useCreateMetrics hook wraps the creation in a memoization.
The useCreateMetrics hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.
If your create function contains other dependencies, the changing of which should also cause the Metrics object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Metrics object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Metrics object at the top level of a React application. Even though the App component is rendered twice, the Metrics object creation only occurs once by default.
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const metrics = useCreateMetrics(store, (store) => {
console.log('Metrics created');
return createMetrics(store).setMetricDefinition(
'speciesCount',
'species',
);
});
return <span>{metrics.getMetric('speciesCount')}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Metrics created'
root.render(<App />);
// No second Metrics creation
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Metrics object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateMetrics hook takes the tableToCount prop as a dependency, and so the Metrics object is created again on the second render.
const App = ({tableToCount}) => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {fido: {species: 'dog'}})
.setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const metrics = useCreateMetrics(
store,
(store) => {
console.log(`Count created for ${tableToCount} table`);
return createMetrics(store).setMetricDefinition(
'tableCount',
tableToCount,
);
},
[tableToCount],
);
return <span>{metrics.getMetric('tableCount')}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App tableToCount="pets" />);
// -> 'Count created for pets table'
console.log(app.innerHTML);
// -> '<span>1</span>'
root.render(<App tableToCount="species" />);
// -> 'Count created for species table'
console.log(app.innerHTML);
// -> '<span>2</span>'
useMetric
The useMetric hook gets the current value of a Metric, and registers a listener so that any changes to that result will cause a re-render.
useMetric(
metricId: string,
metricsOrMetricsId?: MetricsOrMetricsId,
): number | undefined| Type | Description | |
|---|---|---|
metricId | string | |
metricsOrMetricsId? | MetricsOrMetricsId | The |
| returns | number | undefined | The numeric value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Metrics object or a set of Metrics objects named by Id. The useMetric hook lets you indicate which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or provide a Metrics object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Metric will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Metrics object outside the application, which is used in the useMetric hook by reference. A change to the Metric re-renders the component.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => <span>{useMetric('highestPrice', metrics)}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>5</span>'
store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<span>20</span>'
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetric hook.
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetric('highestPrice')}</span>;
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5</span>'
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetric hook.
const App = ({metrics}) => (
<Provider metricsById={{petMetrics: metrics}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetric('highestPrice', 'petMetrics')}</span>;
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5</span>'
useMetricIds
The useMetricIds hook gets an array of the Metric Ids registered with a Metrics object, and registers a listener so that any changes to that result will cause a re-render.
useMetricIds(metricsOrMetricsId?: MetricsOrMetricsId): Ids| Type | Description | |
|---|---|---|
metricsOrMetricsId? | MetricsOrMetricsId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Metrics object or a set of Metrics objects named by Id. The useMetricIds hook lets you indicate which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or provide a Metrics object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Metric Ids in the Metrics object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Metrics object outside the application, which is used in the useMetricIds hook by reference. A newly-registered Metric re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const metrics = createMetrics(store);
const App = () => <span>{JSON.stringify(useMetricIds(metrics))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addMetricDefinition = () =>
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
addMetricDefinition();
console.log(app.innerHTML);
// -> '<span>["highestPrice"]</span>'
Since
v4.1.0
useMetricListener
The useMetricListener hook registers a listener function with the Metrics object that will be called whenever the value of a specified Metric changes.
useMetricListener(
metricId: IdOrNull,
listener: MetricListener,
listenerDeps?: DependencyList,
metricsOrMetricsId?: MetricsOrMetricsId,
): void| Type | Description | |
|---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
metricsOrMetricsId? | MetricsOrMetricsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useMetric hook).
You can either listen to a single Metric (by specifying the Metric Id as the method's first parameter), or changes to any Metric (by providing a null wildcard).
Unlike the addMetricListener method, which returns a listener Id and requires you to remove it manually, the useMetricListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Metrics object, will be deleted.
Example
This example uses the useMetricListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Metrics object.
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => {
useMetricListener('highestPrice', () => console.log('Metric changed'));
return <span>App</span>;
};
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App metrics={metrics} />);
console.log(metrics.getListenerStats().metric);
// -> 1
store.setCell('species', 'horse', 'price', 20);
// -> 'Metric changed'
root.unmount();
console.log(metrics.getListenerStats().metric);
// -> 0
useMetrics
The useMetrics hook is used to get a reference to a Metrics object from within a Provider component context.
useMetrics(id?: string): Metrics | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Metrics | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Metrics object (or a set of Metrics objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useMetrics hook lets you either get a reference to the default Metrics object (when called without a parameter), or one of the Metrics objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetrics hook to get a reference to the Metrics object again, without the need to have it passed as a prop.
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => <span>{useMetrics().getListenerStats().metric}</span>;
const metrics = createMetrics(createStore());
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Metrics object is provided, named by Id. A component within it then uses the useMetrics hook with that Id to get a reference to the Metrics object again, without the need to have it passed as a prop.
const App = ({metrics}) => (
<Provider metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useMetrics('petStore').getListenerStats().metric}</span>
);
const metrics = createMetrics(createStore());
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
useMetricsIds
The useMetricsIds hook is used to retrieve the Ids of all the named Metrics objects present in the current Provider component context.
useMetricsIds(): IdsExample
This example adds two named Metrics objects to a Provider context and an inner component accesses their Ids.
const App = ({store}) => {
const store1 = useCreateStore(createStore);
const metrics1 = useCreateMetrics(store1, createMetrics);
const store2 = useCreateStore(createStore);
const metrics2 = useCreateMetrics(store2, createMetrics);
return (
<Provider metricsById={{metrics1, metrics2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useMetricsIds())}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["metrics1","metrics2"]</span>'
Since
v4.1.0
useMetricsOrMetricsById
The useMetricsOrMetricsById hook is used to get a reference to a Metrics object from within a Provider component context, or have it passed directly to this hook.
useMetricsOrMetricsById(metricsOrMetricsId?: MetricsOrMetricsId): Metrics | undefined| Type | Description | |
|---|---|---|
metricsOrMetricsId? | MetricsOrMetricsId | Either an |
| returns | Metrics | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Metrics object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Metrics-based components).
This hook is unlikely to be used often. For most situations, you will want to use the useMetrics hook.
Example
This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetricsOrMetricsById hook to get a reference to the Metrics object again, without the need to have it passed as a prop. Note however, that unlike the useMetrics hook example, this component would also work if you were to pass the Metrics object directly into it, making it more portable.
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = ({metrics}) => (
<span>
{JSON.stringify(useMetricsOrMetricsById(metrics).getMetricIds())}
</span>
);
const metrics = createMetrics(
createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
}),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>["highestPrice"]</span>'
Since
v4.1.0
Persister hooks
This is the collection of persister hooks within the ui-react module. There is only one function, useCreatePersister.
useCreatePersister
The useCreatePersister hook is used to create a Persister within a React application along with convenient memoization and callbacks.
useCreatePersister<PersisterOrUndefined>(
store: Store,
create: (store: Store) => PersisterOrUndefined,
createDeps?: DependencyList,
then?: (persister: PersisterOrUndefined) => Promise<void>,
thenDeps?: DependencyList,
): PersisterOrUndefined| Type | Description | |
|---|---|---|
store | Store | A reference to the |
create | (store: Store) => PersisterOrUndefined | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
then? | (persister: PersisterOrUndefined) => Promise<void> | An optional callback for performing asynchronous post-creation steps on the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | PersisterOrUndefined | A reference to the |
It is possible to create a Persister outside of the React app with the regular createPersister function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Persister being created every time the app renders or re-renders, the useCreatePersister hook wraps the creation in a memoization, and provides a second callback so that you can configure the Persister, once, and asynchronously, when it is created.
If your create function (the second parameter to the hook) contains dependencies, the changing of which should cause the Persister to be recreated, you can provide them in an array in the third parameter, just as you would for any React hook with dependencies. The Store passed in as the first parameter of this hook is used as a dependency by default.
A second then callback can be provided as the fourth parameter. This is called after the creation, and, importantly, can be asynchronous, so that you can configure the Persister with the startAutoLoad method and startAutoSave method, for example. If this callback contains dependencies, the changing of which should cause the Persister to be reconfigured, you can provide them in an array in the fifth parameter. The Persister itself is used as a dependency by default.
Since v4.3.0, the create function can return undefined, meaning that you can enable or disable persistence conditionally within this hook. This is useful for applications which might turn on or off their cloud persistence or collaboration features. As a result, the then callback may also get passed undefined, which you should handle accordingly.
This hook ensures the Persister object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Persister at the top level of a React application. Even though the App component is rendered twice, the Persister creation only occurs once by default.
const App = () => {
const store = useCreateStore(createStore);
const persister = useCreatePersister(
store,
(store) => {
console.log('Persister created');
return createSessionPersister(store, 'pets');
},
[],
async (persister) => {
await persister.startAutoLoad();
await persister.startAutoSave();
},
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
sessionStorage.setItem(
'pets',
'[{"pets":{"fido":{"species":"dog"}}}, {}]',
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Persister created'
// ...
root.render(<App />);
// No second Persister creation
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
root.unmount();
This example creates a Persister at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreatePersister hook takes the sessionKey prop as a dependency, and so the Persister object is created again on the second render.
const App = ({sessionKey}) => {
const store = useCreateStore(createStore);
const persister = useCreatePersister(
store,
(store) => {
console.log(`Persister created for session key ${sessionKey}`);
return createSessionPersister(store, sessionKey);
},
[sessionKey],
async (persister) => {
await persister.startAutoLoad();
await persister.startAutoSave();
},
);
return <span>{JSON.stringify(useTables(store))}</span>;
};
sessionStorage.setItem(
'fidoStore',
'[{"pets":{"fido":{"species":"dog"}}}, {}]',
);
sessionStorage.setItem(
'cujoStore',
'[{"pets":{"cujo":{"species":"dog"}}}, {}]',
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App sessionKey="fidoStore" />);
// -> 'Persister created for session key fidoStore'
// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'
root.render(<App sessionKey="cujoStore" />);
// -> 'Persister created for session key cujoStore'
// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"cujo\":{\"species\":\"dog\"}}}</span>'
root.unmount();
Queries hooks
This is the collection of queries hooks within the ui-react module. There are 21 queries hooks in total.
useResultTable
The useResultTable hook returns an object containing the entire data of the ResultTable of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultTable(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Table| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Table | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultTable hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the query result will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useTable hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultTable('dogColors', queries))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultTable hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultTable hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
Since
v2.0.0
useResultTableCellIds
The useResultTableCellIds hook returns the Ids of every Cell used across the whole ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultTableCellIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultTableCellIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultTableCellIds hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColorsAndLegs',
'pets',
({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultTableCellIds('dogColorsAndLegs', queries))}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'cujo', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultTableCellIds hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTableCellIds('dogColorsAndLegs'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
}),
).setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultTableCellIds hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultTableCellIds('dogColorsAndLegs', 'petQueries'),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black', legs: 4},
}),
).setQueryDefinition('dogColorsAndLegs', 'pets', ({select, where}) => {
select('color');
select('legs');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["color","legs"]</span>'
Since
v4.1.0
useResultTableCellIdsListener
The useResultTableCellIdsListener hook registers a listener function with a Queries object that will be called whenever the Cell Ids that appear anywhere in a ResultTable change.
useResultTableCellIdsListener(
queryId: IdOrNull,
listener: ResultTableCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultTableCellIds hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultTableCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultTableCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultTableCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableCellIdsListener('petColorsAndLegs', () =>
console.log('Result Cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColorsAndLegs',
'pets',
({select}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().tableCellIds);
// -> 1
store.setCell('pets', 'cujo', 'legs', 4);
// -> 'Result Cell Ids changed'
root.unmount();
console.log(queries.getListenerStats().tableCellIds);
// -> 0
Since
v4.1.0
useResultTableListener
The useResultTableListener hook registers a listener function with a Queries object that will be called whenever data in a ResultTable changes.
useResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultTable hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultTableListener method, which returns a listener Id and requires you to remove it manually, the useResultTableListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultTableListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableListener('petColors', () =>
console.log('Result table changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result table changed'
root.unmount();
console.log(queries.getListenerStats().table);
// -> 0
Since
v2.0.0
useResultRowIds
The useResultRowIds hook returns the Ids of every Row in the ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultRowIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultRowIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultRowIds hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', queries))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultRowIds hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultRowIds hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v2.0.0
useResultRowIdsListener
The useResultRowIdsListener hook registers a listener function with a Queries object that will be called whenever the Row Ids in a ResultTable change.
useResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRowIds hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowIdsListener('petColors', () =>
console.log('Result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row Ids changed'
root.unmount();
console.log(queries.getListenerStats().rowIds);
// -> 0
Since
v2.0.0
useResultSortedRowIds
The useResultSortedRowIds hook returns the sorted (and optionally, paginated) Ids of every Row in the ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultSortedRowIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted result Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultSortedRowIds hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
queries,
),
)}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultSortedRowIds hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultSortedRowIds('dogColors', 'color'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultSortedRowIds hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
'petQueries',
),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
Since
v2.0.0
useResultSortedRowIdsListener
The useResultSortedRowIdsListener hook registers a listener function with a Queries object that will be called whenever the sorted (and optionally, paginated) Row Ids in a ResultTable change.
useResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultSortedRowIds hook).
Unlike the addResultSortedRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultSortedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultSortedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultSortedRowIdsListener(
'petColors',
'color',
false,
0,
undefined,
() => console.log('Sorted result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {color: 'tan'});
// -> 'Sorted result Row Ids changed'
root.unmount();
console.log(queries.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
useResultRow
The useResultRow hook returns an object containing the data of a single Row in the ResultTable of the given query, and registers a listener so that any changes to that Row will cause a re-render.
useResultRow(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Row| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Row | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultRow hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultRow hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido', queries))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultRow hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultRow hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultRow('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v2.0.0
useResultRowCount
The useResultRowCount hook returns the count of the Row objects in the ResultTable of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultRowCount(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): number| Type | Description | |
|---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | number | The number of |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultRowCount hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the count of ResultRow objects will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultRowCount hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => <span>{useResultRowCount('dogColors', queries)}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>2</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>1</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultRowCount hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useResultRowCount('dogColors')}</span>;
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultRowCount hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultRowCount('dogColors', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>2</span>'
Since
v4.1.0
useResultRowCountListener
The useResultRowCountListener hook registers a listener function with a Queries object that will be called whenever the count of ResultRow objects in a ResultTable changes.
useResultRowCountListener(
queryId: IdOrNull,
listener: ResultRowCountListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
listener | ResultRowCountListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRowCount hook).
You can either listen to a single ResultTable (by specifying a query Id as the method's first parameter) or changes to any ResultTable (by providing a null wildcard).
Unlike the addResultRowCountListener method, which returns a listener Id and requires you to remove it manually, the useResultRowCountListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultRowCountListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowCountListener('petColors', () =>
console.log('Result Row count changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().rowCount);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row count changed'
root.unmount();
console.log(queries.getListenerStats().rowCount);
// -> 0
Since
v4.1.0
useResultRowListener
The useResultRowListener hook registers a listener function with a Queries object that will be called whenever data in a result Row changes.
useResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultRow hook).
You can either listen to a single result Row (by specifying a query Id and Row Id as the method's first two parameters) or changes to any result Row (by providing null wildcards).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific result Row in a specific query, any result Row in a specific query, a specific result Row in any query, or any result Row in any query.
Unlike the addResultRowListener method, which returns a listener Id and requires you to remove it manually, the useResultRowListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultRowListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowListener('petColors', 'fido', () =>
console.log('Result row changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result row changed'
root.unmount();
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
useResultCellIds
The useResultCellIds hook returns the Ids of every Cell in a given Row in the ResultTable of the given query, and registers a listener so that any changes to those Ids will cause a re-render.
useResultCellIds(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultCellIds hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultCellIds hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', queries))}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
store.setCell('pets', 'fido', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["species","color","legs"]</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultCellIds hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultCellIds('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultCellIds hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
Since
v2.0.0
useResultCellIdsListener
The useResultCellIdsListener hook registers a listener function with a Queries object that will be called whenever the Cell Ids in a result Row change.
useResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultCellIds hook).
Both, either, or neither of the queryId and rowId parameters can be wildcarded with null. You can listen to a specific result Row in a specific query, any result Row in a specific query, a specific result Row in any query, or any result Row in any query.
Unlike the addResultCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useResultCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellIdsListener('petColors', 'fido', () =>
console.log('Result cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select, where}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'legs', 4);
// -> 'Result cell Ids changed'
root.unmount();
console.log(queries.getListenerStats().cellIds);
// -> 0
Since
v2.0.0
useResultCell
The useResultCell hook returns the value of a single Cell in a given Row in the ResultTable of the given query, and registers a listener so that any changes to that value will cause a re-render.
useResultCell(
queryId: string,
rowId: string,
cellId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Cell | undefined| Type | Description | |
|---|---|---|
queryId | string | The |
rowId | string | The |
cellId | string | |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | Cell | undefined | The value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useResultCell hook lets you indicate which Queries object to get data for: omit the final optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries object outside the application, which is used in the useResultCell hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>{useResultCell('dogColors', 'fido', 'color', queries)}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useResultCell hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useResultCell hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useResultCellListener
The useResultCellListener hook registers a listener function with a Queries object that will be called whenever data in a Cell changes.
useResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void| Type | Description | |
|---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useResultCell hook).
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the queryId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific result Row in a specific query, any Cell in any result Row in any query, for example - or every other combination of wildcards.
Unlike the addResultCellListener method, which returns a listener Id and requires you to remove it manually, the useResultCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Queries object will be deleted.
Example
This example uses the useResultCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellListener('petColors', 'fido', 'color', () =>
console.log('Result cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(queries.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result cell changed'
root.unmount();
console.log(queries.getListenerStats().cell);
// -> 0
Since
v2.0.0
useCreateQueries
The useCreateQueries hook is used to create a Queries object within a React application with convenient memoization.
useCreateQueries(
store: Store,
create: (store: Store) => Queries,
createDeps?: DependencyList,
): Queries| Type | Description | |
|---|---|---|
store | Store | A reference to the |
create | (store: Store) => Queries | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Queries | A reference to the |
It is possible to create a Queries object outside of the React app with the regular createQueries function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Queries object being created every time the app renders or re-renders, the useCreateQueries hook wraps the creation in a memoization.
The useCreateQueries hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.
If your create function contains other dependencies, the changing of which should also cause the Queries object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Queries object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Queries object at the top level of a React application. Even though the App component is rendered twice, the Queries object creation only occurs once by default.
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Queries created'
root.render(<App />);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Queries object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateQueries hook takes the resultCell prop as a dependency, and so the Queries object is created again on the second render.
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Queries created'
root.render(<App />);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useQueries
The useQueries hook is used to get a reference to a Queries object from within a Provider component context.
useQueries(id?: string): Queries | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Queries | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Queries object (or a set of Queries objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useQueries hook lets you either get a reference to the default Queries object (when called without a parameter), or one of the Queries objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useQueries hook to get a reference to the Queries object again, without the need to have it passed as a prop.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useQueries().getListenerStats().table}</span>;
const queries = createQueries(createStore());
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Queries object is provided, named by Id. A component within it then uses the useQueries hook with that Id to get a reference to the Queries object again, without the need to have it passed as a prop.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useQueries('petQueries').getListenerStats().table}</span>
);
const queries = createQueries(createStore());
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v2.0.0
useQueriesIds
The useQueriesIds hook is used to retrieve the Ids of all the named Queries objects present in the current Provider component context.
useQueriesIds(): IdsExample
This example adds two named Queries objects to a Provider context and an inner component accesses their Ids.
const App = ({store}) => {
const store1 = useCreateStore(createStore);
const queries1 = useCreateQueries(store1, createQueries);
const store2 = useCreateStore(createStore);
const queries2 = useCreateQueries(store2, createQueries);
return (
<Provider queriesById={{queries1, queries2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useQueriesIds())}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["queries1","queries2"]</span>'
Since
v4.1.0
useQueriesOrQueriesById
The useQueriesOrQueriesById hook is used to get a reference to a Queries object from within a Provider component context, or have it passed directly to this hook.
useQueriesOrQueriesById(queriesOrQueriesId?: QueriesOrQueriesId): Queries | undefined| Type | Description | |
|---|---|---|
queriesOrQueriesId? | QueriesOrQueriesId | |
| returns | Queries | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Queries object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Queries-based components).
This is unlikely to be used often. For most situations, you will want to use the useQueries hook.
Example
This example creates a Provider context into which a default Queries object is provided. A component within it then uses the useQueriesOrQueriesById hook to get a reference to the Queries object again, without the need to have it passed as a prop. Note however, that unlike the useQueries hook example, this component would also work if you were to pass the Queries object directly into it, making it more portable.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = ({queries}) => (
<span>
{JSON.stringify(useQueriesOrQueriesById(queries).getQueryIds())}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>["dogColors"]</span>'
Since
v4.1.0
useQueryIds
The useQueryIds hook gets an array of the Query Ids registered with a Queries object, and registers a listener so that any changes to that result will cause a re-render.
useQueryIds(queriesOrQueriesId?: QueriesOrQueriesId): Ids| Type | Description | |
|---|---|---|
queriesOrQueriesId? | QueriesOrQueriesId | |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Queries object or a set of Queries objects named by Id. The useQueryIds hook lets you indicate which Queries object to get data for: omit the optional final parameter for the default context Queries object, provide an Id for a named context Queries object, or provide a Queries object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Query Ids in the Queries object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Queries object outside the application, which is used in the useQueryIds hook by reference. A newly-registered Relationship re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
const App = () => <span>{JSON.stringify(useQueryIds(queries))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addQueryDefinition = () =>
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
addQueryDefinition();
console.log(app.innerHTML);
// -> '<span>["dogColors"]</span>'
Since
v4.1.0
Relationships hooks
This is the collection of relationships hooks within the ui-react module. There are 11 relationships hooks in total.
useLinkedRowIds
The useLinkedRowIds hook gets the linked Row Ids for a given Row in a linked list Relationship, and registers a listener so that any changes to that result will cause a re-render.
useLinkedRowIds(
relationshipId: string,
firstRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Ids | The linked |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useLinkedRowIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the linked Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships object outside the application, which is used in the useLinkedRowIds hook by reference. A change to the linked Row Ids re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const App = () => (
<span>
{JSON.stringify(useLinkedRowIds('petSequence', 'fido', relationships))}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo","toto"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLinkedRowIds hook.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useLinkedRowIds('petSequence', 'fido'))}</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLinkedRowIds hook.
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useLinkedRowIds('petSequence', 'fido', 'petRelationships'),
)}
</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
useLinkedRowIdsListener
The useLinkedRowIdsListener hook registers a listener function with the Relationships object that will be called whenever the linked Row Ids in a Relationship change.
useLinkedRowIdsListener(
relationshipId: string,
firstRowId: string,
listener: LinkedRowIdsListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void| Type | Description | |
|---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLinkedRowsId hook).
Unlike other listener registration methods, you cannot provide null wildcards for the first two parameters of the useLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store.
Unlike the addLinkedRowsIdListener method, which returns a listener Id and requires you to remove it manually, the useLinkedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useLinkedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useLinkedRowIdsListener('petSequence', 'fido', () =>
console.log('Linked Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', next: 'felix'},
felix: {species: 'cat', next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().linkedRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'Linked Row Ids changed'
root.unmount();
console.log(relationships.getListenerStats().linkedRowIds);
// -> 0
useLocalRowIds
The useLocalRowIds hook gets the local Row Ids for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
useLocalRowIds(
relationshipId: string,
remoteRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids| Type | Description | |
|---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Ids | The local |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useLocalRowIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the local Row Id will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships object outside the application, which is used in the useLocalRowIds hook by reference. A change to the local Row Ids re-renders the component.
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<span>
{JSON.stringify(useLocalRowIds('petSpecies', 'dog', relationships))}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLocalRowIds hook.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useLocalRowIds('petSpecies', 'dog'))}</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLocalRowIds hook.
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useLocalRowIds('petSpecies', 'dog', 'petRelationships'),
)}
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
useLocalRowIdsListener
The useLocalRowIdsListener hook registers a listener function with the Relationships object that will be called whenever the local Row Ids in a Relationship change.
useLocalRowIdsListener(
relationshipId: IdOrNull,
remoteRowId: IdOrNull,
listener: LocalRowIdsListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLocalRowsId hook).
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and remoteRowId parameters can be wildcarded with null. You can listen to a specific remote Row in a specific Relationship, any remote Row in a specific Relationship, a specific remote Row in any Relationship, or any remote Row in any Relationship.
Unlike the addLocalRowsIdListener method, which returns a listener Id and requires you to remove it manually, the useLocalRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useLocalRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useLocalRowIdsListener('petSpecies', 'dog', () =>
console.log('Local Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().localRowIds);
// -> 1
store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Local Row Ids changed'
root.unmount();
console.log(relationships.getListenerStats().localRowIds);
// -> 0
useRemoteRowId
The useRemoteRowId hook gets the remote Row Id for a given local Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
useRemoteRowId(
relationshipId: string,
localRowId: string,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Id | undefined| Type | Description | |
|---|---|---|
relationshipId | string | The |
localRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Id | undefined | The remote |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useRemoteRowId hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the remote Row Id will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Relationships object outside the application, which is used in the useRemoteRowId hook by reference. A change to the remote Row Id re-renders the component.
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<span>{useRemoteRowId('petSpecies', 'cujo', relationships)}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>wolf</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRemoteRowId hook.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRemoteRowId('petSpecies', 'cujo')}</span>;
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRemoteRowId hook.
const App = ({relationships}) => (
<Provider relationshipsById={{petRelationships: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useRemoteRowId('petSpecies', 'cujo', 'petRelationships')}</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>dog</span>'
useRemoteRowIdListener
The useRemoteRowIdListener hook registers a listener function with the Relationships object that will be called whenever a remote Row Id in a Relationship changes.
useRemoteRowIdListener(
relationshipId: IdOrNull,
localRowId: IdOrNull,
listener: RemoteRowIdListener,
listenerDeps?: DependencyList,
relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void| Type | Description | |
|---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRemoteRowId hook).
You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).
Both, either, or neither of the relationshipId and localRowId parameters can be wildcarded with null. You can listen to a specific local Row in a specific Relationship, any local Row in a specific Relationship, a specific local Row in any Relationship, or any local Row in any Relationship.
Unlike the addRemoteRowIdListener method, which returns a listener Id and requires you to remove it manually, the useRemoteRowIdListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.
Example
This example uses the useRemoteRowIdListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => {
useRemoteRowIdListener('petSpecies', 'cujo', () =>
console.log('Remote Row Id changed'),
);
return <span>App</span>;
};
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(relationships.getListenerStats().remoteRowId);
// -> 1
store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'Remote Row Id changed'
root.unmount();
console.log(relationships.getListenerStats().remoteRowId);
// -> 0
useCreateRelationships
The useCreateRelationships hook is used to create a Relationships object within a React application with convenient memoization.
useCreateRelationships(
store: Store,
create: (store: Store) => Relationships,
createDeps?: DependencyList,
): Relationships| Type | Description | |
|---|---|---|
store | Store | A reference to the |
create | (store: Store) => Relationships | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Relationships | A reference to the |
It is possible to create a Relationships object outside of the React app with the regular createRelationships function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Relationships object being created every time the app renders or re-renders, the useCreateRelationships hook wraps the creation in a memoization.
The useCreateRelationships hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.
If your create function contains other dependencies, the changing of which should also cause the Relationships object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
This hook ensures the Relationships object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Relationships object at the top level of a React application. Even though the App component is rendered twice, the Relationships object creation only occurs once by default.
const App = () => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
})
.setTable('species', {dog: {price: 5}, cat: {price: 4}}),
);
const relationships = useCreateRelationships(store, (store) => {
console.log('Relationships created');
return createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
});
return <span>{relationships.getRemoteRowId('petSpecies', 'fido')}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Relationships created'
root.render(<App />);
// No second Relationships creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates a Relationships object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateRelationships hook takes the remoteTableAndCellToLink prop as a dependency, and so the Relationships object is created again on the second render.
const App = ({remoteTableAndCellToLink}) => {
const store = useCreateStore(() =>
createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'brown'},
})
.setTable('species', {dog: {price: 5}, cat: {price: 4}})
.setTable('color', {brown: {discount: 0.1}, black: {discount: 0}}),
);
const relationships = useCreateRelationships(
store,
(store) => {
console.log(`Relationship created to ${remoteTableAndCellToLink}`);
return createRelationships(store).setRelationshipDefinition(
'cellLinked',
'pets',
remoteTableAndCellToLink,
remoteTableAndCellToLink,
);
},
[remoteTableAndCellToLink],
);
return <span>{relationships.getRemoteRowId('cellLinked', 'fido')}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App remoteTableAndCellToLink="species" />);
// -> 'Relationship created to species'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App remoteTableAndCellToLink="color" />);
// -> 'Relationship created to color'
console.log(app.innerHTML);
// -> '<span>brown</span>'
useRelationshipIds
The useRelationshipIds hook gets an array of the Relationship Ids registered with a Relationships object, and registers a listener so that any changes to that result will cause a re-render.
useRelationshipIds(relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId): Ids| Type | Description | |
|---|---|---|
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
| returns | Ids | The |
A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useRelationshipIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Relationship Ids in the Relationships object will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Example
This example creates an Relationships object outside the application, which is used in the useRelationshipIds hook by reference. A newly-registered Relationship re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
const App = () => (
<span>{JSON.stringify(useRelationshipIds(relationships))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>[]</span>'
const addRelationshipDefinition = () =>
relationships.setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
addRelationshipDefinition();
console.log(app.innerHTML);
// -> '<span>["petSpecies"]</span>'
Since
v4.1.0
useRelationships
The useRelationships hook is used to get a reference to a Relationships object from within a Provider component context.
useRelationships(id?: string): Relationships | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Relationships | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Relationships object (or a set of Relationships objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useRelationships hook lets you either get a reference to the default Relationships object (when called without a parameter), or one of the Relationships objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRelationships hook to get a reference to the Relationships object again, without the need to have it passed as a prop.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useRelationships().getListenerStats().remoteRowId}</span>
);
const relationships = createRelationships(createStore());
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Relationships object is provided, named by Id. A component within it then uses the useRelationships hook with that Id to get a reference to the Relationships object again, without the need to have it passed as a prop.
const App = ({relationships}) => (
<Provider relationshipsById={{petStore: relationships}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{useRelationships('petStore').getListenerStats().remoteRowId}
</span>
);
const relationships = createRelationships(createStore());
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
useRelationshipsIds
The useRelationshipsIds hook is used to retrieve the Ids of all the named Relationships objects present in the current Provider component context.
useRelationshipsIds(): IdsExample
This example adds two named Relationships objects to a Provider context and an inner component accesses their Ids.
const App = ({store}) => {
const store1 = useCreateStore(createStore);
const relationships1 = useCreateRelationships(
store1,
createRelationships,
);
const store2 = useCreateStore(createStore);
const relationships2 = useCreateRelationships(
store2,
createRelationships,
);
return (
<Provider relationshipsById={{relationships1, relationships2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useRelationshipsIds())}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["relationships1","relationships2"]</span>'
Since
v4.1.0
useRelationshipsOrRelationshipsById
The useRelationshipsOrRelationshipsById hook is used to get a reference to a Relationships object from within a Provider component context, or have it passed directly to this hook.
useRelationshipsOrRelationshipsById(relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId): Relationships | undefined| Type | Description | |
|---|---|---|
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | Either an |
| returns | Relationships | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Relationships object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Relationships-based components).
This is unlikely to be used often. For most situations, you will want to use the useRelationships hook.
Example
This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRelationshipsOrRelationshipsById hook to get a reference to the Relationships object again, without the need to have it passed as a prop. Note however, that unlike the useRelationships hook example, this component would also work if you were to pass the Relationships object directly into it, making it more portable.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = ({relationships}) => (
<span>
{JSON.stringify(
useRelationshipsOrRelationshipsById(
relationships,
).getRelationshipIds(),
)}
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<span>["petSpecies"]</span>'
Since
v4.1.0
Store hooks
This is the collection of store hooks within the ui-react module. There are 48 store hooks in total.
useCreateStore
The useCreateStore hook is used to create a Store within a React application with convenient memoization.
useCreateStore(
create: () => Store,
createDeps?: DependencyList,
): Store| Type | Description | |
|---|---|---|
create | () => Store | A function for performing the creation of the |
createDeps? | DependencyList | An optional array of dependencies for the |
| returns | Store | A reference to the |
It is possible to create a Store outside of the React app with the regular createStore function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Store being created every time the app renders or re-renders, the useCreateStore hook wraps the creation in a memoization.
The useCreateStore hook is a very thin wrapper around the React useMemo hook, defaulting to an empty array for its dependencies, so that by default, the creation only occurs once.
If your create function contains other dependencies, the changing of which should cause the Store to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
Examples
This example creates an empty Store at the top level of a React application. Even though the App component is rendered twice, the Store creation only occurs once by default.
const App = () => {
const store = useCreateStore(() => {
console.log('Store created');
return createStore().setTables({pets: {fido: {species: 'dog'}}});
});
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
// -> 'Store created'
root.render(<App />);
// No second Store creation
console.log(app.innerHTML);
// -> '<span>dog</span>'
This example creates an empty Store at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateStore hook takes the fidoSpecies prop as a dependency, and so the Store is created again on the second render.
const App = ({fidoSpecies}) => {
const store = useCreateStore(() => {
console.log(`Store created for fido as ${fidoSpecies}`);
return createStore().setTables({pets: {fido: {species: fidoSpecies}}});
}, [fidoSpecies]);
return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App fidoSpecies="dog" />);
// -> 'Store created for fido as dog'
console.log(app.innerHTML);
// -> '<span>dog</span>'
root.render(<App fidoSpecies="cat" />);
// -> 'Store created for fido as cat'
console.log(app.innerHTML);
// -> '<span>cat</span>'
useStore
The useStore hook is used to get a reference to a Store from within a Provider component context.
useStore(id?: string): Store | undefined| Type | Description | |
|---|---|---|
id? | string | An optional |
| returns | Store | undefined | A reference to the |
A Provider component is used to wrap part of an application in a context. It can contain a default Store (or a set of Store objects named by Id) that can be easily accessed without having to be passed down as props through every component.
The useStore hook lets you either get a reference to the default Store (when called without a parameter), or one of the Store objects that are named by Id (when called with an Id parameter).
Examples
This example creates a Provider context into which a default Store is provided. A component within it then uses the useStore hook to get a reference to the Store again, without the need to have it passed as a prop.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useStore().getListenerStats().tables}</span>;
const store = createStore();
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useStore hook with that Id to get a reference to the Store again, without the need to have it passed as a prop.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useStore('petStore').getListenerStats().tables}</span>
);
const store = createStore();
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>0</span>'
useStoreIds
The useStoreIds hook is used to retrieve the Ids of all the named Store objects present in the current Provider component context.
useStoreIds(): IdsExample
This example adds two named Store objects to a Provider context and an inner component accesses their Ids.
const App = ({store}) => {
const store1 = useCreateStore(createStore);
const store2 = useCreateStore(createStore);
return (
<Provider storesById={{store1, store2}}>
<Pane />
</Provider>
);
};
const Pane = () => <span>{JSON.stringify(useStoreIds())}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["store1","store2"]</span>'
Since
v4.1.0
useStoreOrStoreById
The useStoreOrStoreById hook is used to get a reference to a Store object from within a Provider component context, or have it passed directly to this hook.
useStoreOrStoreById(storeOrStoreId?: StoreOrStoreId): Store | undefined| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | Either an |
| returns | Store | undefined | A reference to the |
This is mostly of use when you are developing a component that needs a Store object and which might have been passed in explicitly to the component or is to be picked up from the context by Id (a common pattern for Store-based components).
This is unlikely to be used often. For most situations, you will want to use the useStore hook.
Example
This example creates a Provider context into which a default Store object is provided. A component within it then uses the useStoreOrStoreById hook to get a reference to the Store object again, without the need to have it passed as a prop. Note however, that unlike the useStore hook example, this component would also work if you were to pass the Store object directly into it, making it more portable.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = ({store}) => (
<span>{JSON.stringify(useStoreOrStoreById(store).getTableIds())}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
Since
v4.1.0
useDelTablesCallback
The useDelTablesCallback hook returns a callback that can be used to remove all of the tabular data in a Store.
useDelTablesCallback(
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelTablesCallback hook to create an event handler which deletes from the Store when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelTablesCallback(store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
useSetTablesCallback
The useSetTablesCallback hook returns a parameterized callback that can be used to set the tabular data of a Store.
useSetTablesCallback<Parameter>(
getTables: (parameter: Parameter, store: Store) => Tables,
getTablesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, tables: Tables) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getTables | (parameter: Parameter, store: Store) => Tables | A function which returns the |
getTablesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, tables: Tables) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The first parameter is a function which will produce the Tables object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetTablesCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useSetTablesCallback(
(e) => ({pets: {nemo: {species: 'fish', bubbles: e.bubbles}}}),
[],
store,
(store, tables) => console.log(`Updated: ${JSON.stringify(tables)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"pets":{"nemo":{"species":"fish","bubbles":true}}}'
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish","bubbles":true}}}'
useTables
The useTables hook returns a Tables object containing the tabular data of a Store, and registers a listener so that any changes to that result will cause a re-render.
useTables(storeOrStoreId?: StoreOrStoreId): Tables| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Tables |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTables hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Tables will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTables hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTables(store))}</span>;
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"walnut"}}}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTables hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTables())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTables hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTables('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
useTablesListener
The useTablesListener hook registers a listener function with a Store that will be called whenever tabular data in it changes.
useTablesListener(
listener: TablesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TablesListener | The function that will be called whenever tabular data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTables hook).
Unlike the addTablesListener method, which returns a listener Id and requires you to remove it manually, the useTablesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTablesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTablesListener(() => console.log('Tables changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tables);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
root.unmount();
console.log(store.getListenerStats().tables);
// -> 0
useTableIds
The useTableIds hook returns the Ids of every Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
useTableIds(storeOrStoreId?: StoreOrStoreId): Ids| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTableIds hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTableIds hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTableIds(store))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
store.setCell('species', 'dog', 'price', 5);
console.log(app.innerHTML);
// -> '<span>["pets","species"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTableIds hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds())}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTableIds hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds('petStore'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
useTableIdsListener
The useTableIdsListener hook registers a listener function with a Store that will be called whenever the Table Ids in it change.
useTableIdsListener(
listener: TableIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TableIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableIds hook).
Unlike the addTableIdsListener method, which returns a listener Id and requires you to remove it manually, the useTableIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTableIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableIdsListener(() => console.log('Table Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tableIds);
// -> 1
store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
root.unmount();
console.log(store.getListenerStats().tableIds);
// -> 0
useDelTableCallback
The useDelTableCallback hook returns a callback that can be used to remove a single Table from a Store.
useDelTableCallback(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelTableCallback hook to create an event handler which deletes from the Store when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelTableCallback('pets', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
useSetTableCallback
The useSetTableCallback hook returns a parameterized callback that can be used to set the data of a single Table in a Store.
useSetTableCallback<Parameter>(
tableId: string,
getTable: (parameter: Parameter, store: Store) => Table,
getTableDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, table: Table) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | |
getTable | (parameter: Parameter, store: Store) => Table | A function which returns the |
getTableDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, table: Table) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Table object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetTableCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setTable('pets', {nemo: {species: 'fish'}});
const App = () => {
const handleClick = useSetTableCallback(
'pets',
(e) => ({nemo: {species: 'fish', bubbles: e.bubbles}}),
[],
store,
(store, table) => console.log(`Updated: ${JSON.stringify(table)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTable('pets', store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"nemo":{"species":"fish","bubbles":true}}'
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish","bubbles":true}}'
useTable
The useTable hook returns an object containing the data of a single Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
useTable(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Table| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Table | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTable hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTable hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTable('pets', store))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"}}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTable hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTable('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTable hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useTable('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
useTableCellIds
The useTableCellIds hook returns the Ids of every Cell used across the whole Table, and registers a listener so that any changes to that result will cause a re-render.
useTableCellIds(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids | An array of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTableCellIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Table Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useTableCellIds hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useTableCellIds('pets', store))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'felix', 'species', 'cat');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useTableCellIds hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useTableCellIds('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTableCellIds hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useTableCellIds('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
Since
v3.3.0
useTableCellIdsListener
The useTableCellIdsListener hook registers a listener function with a Store that will be called whenever the Cell Ids that appear anywhere in a Table change.
useTableCellIdsListener(
tableId: IdOrNull,
listener: TableCellIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableCellIds hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).
Unlike the addTableCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useTableCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTableCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableCellIdsListener('pets', () => console.log('Cell Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().tableCellIds);
// -> 1
store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Cell Ids changed'
root.unmount();
console.log(store.getListenerStats().rowIds);
// -> 0
useTableListener
The useTableListener hook registers a listener function with a Store that will be called whenever data in a Table changes.
useTableListener(
tableId: IdOrNull,
listener: TableListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | TableListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTable hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).
Unlike the addTableListener method, which returns a listener Id and requires you to remove it manually, the useTableListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useTableListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useTableListener('pets', () => console.log('Table changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Table changed'
root.unmount();
console.log(store.getListenerStats().table);
// -> 0
useRowIds
The useRowIds hook returns the Ids of every Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRowIds(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRowIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRowIds hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useRowIds('pets', store))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>["fido","felix"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRowIds hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRowIds('pets'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRowIds hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRowIds('pets', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
useRowIdsListener
The useRowIdsListener hook registers a listener function with a Store that will be called whenever the Row Ids in a Table change.
useRowIdsListener(
tableId: IdOrNull,
listener: RowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowIds hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).
Unlike the addRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowIdsListener('pets', () => console.log('Row Ids changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row Ids changed'
root.unmount();
console.log(store.getListenerStats().rowIds);
// -> 0
useSortedRowIds
The useSortedRowIds hook returns the sorted (and optionally, paginated) Ids of every Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useSortedRowIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useSortedRowIds hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, store),
)}
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<span>["felix","fido","cujo"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useSortedRowIds hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSortedRowIds('pets'))}</span>;
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useSortedRowIds hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, 'petStore'),
)}
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
Since
v2.0.0
useSortedRowIdsListener
The useSortedRowIdsListener hook registers a listener function with a Store that will be called whenever sorted (and optionally, paginated) Row Ids in a Table change.
useSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSortedRowIds hook).
Unlike the addSortedRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useSortedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useSortedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useSortedRowIdsListener('pets', 'species', false, 0, undefined, () =>
console.log('Sorted Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids changed'
root.unmount();
console.log(store.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
useAddRowCallback
The useAddRowCallback hook returns a parameterized callback that can be used to create a new Row in a Store.
useAddRowCallback<Parameter>(
tableId: string,
getRow: (parameter: Parameter, store: Store) => Row,
getRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (rowId: undefined | string, store: Store, row: Row) => void,
thenDeps?: DependencyList,
reuseRowIds?: boolean,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (rowId: undefined | string, store: Store, row: Row) => void | A function which is called after the mutation, with the new |
thenDeps? | DependencyList | An optional array of dependencies for the |
reuseRowIds? | boolean | Whether |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Row object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
The reuseRowIds parameter defaults to true, which means that if you delete a Row and then add another, the Id will be re-used - unless you delete the entire Table, in which case all Row Ids will reset. Otherwise, if you specify reuseRowIds to be false, then the Id will be a monotonically increasing string representation of an increasing integer, regardless of any you may have previously deleted.
Example
This example uses the useAddRowCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useAddRowCallback(
'pets',
(e) => ({species: 'frog', bubbles: e.bubbles}),
[],
store,
(rowId, store, row) => console.log(`Added row: ${rowId}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTable('pets', store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Added row: 0'
console.log(span.innerHTML);
// -> '{"0":{"species":"frog","bubbles":true},"nemo":{"species":"fish"}}'
useDelRowCallback
The useDelRowCallback hook returns a callback that can be used to remove a single Row from a Table.
useDelRowCallback(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelRowCallback hook to create an event handler which deletes from the Store when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelRowCallback('pets', 'nemo', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
useRow
The useRow hook returns an object containing the data of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRow(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Row| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Row | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRow hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRow hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useRow('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRow hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useRow('pets', 'fido'))}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRow hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useRow('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
useRowCount
The useRowCount hook returns the count of the Row objects in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useRowCount(
tableId: string,
storeOrStoreId?: StoreOrStoreId,
): number| Type | Description | |
|---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | number |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRowCount hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the count of Row objects will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useRowCount hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useRowCount('pets', store)}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>1</span>'
store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>2</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useRowCount hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRowCount('pets')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>1</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRowCount hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{useRowCount('pets', 'petStore')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>1</span>'
Since
v4.1.0
useRowCountListener
The useRowCountListener hook registers a listener function with a Store that will be called whenever the count of the Row objects in a Table changes.
useRowCountListener(
tableId: IdOrNull,
listener: RowCountListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
listener | RowCountListener | The function that will be called whenever the count of the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowCount hook).
You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).
Unlike the addRowCountListener method, which returns a listener Id and requires you to remove it manually, the useRowCountListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useRowCountListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowCountListener('pets', () => console.log('Row count changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().rowCount);
// -> 1
store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row count changed'
root.unmount();
console.log(store.getListenerStats().rowCount);
// -> 0
Since
v4.1.0
useRowListener
The useRowListener hook registers a listener function with a Store that will be called whenever data in a Row changes.
useRowListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: RowListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRow hook).
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Unlike the addRowListener method, which returns a listener Id and requires you to remove it manually, the useRowListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useRowListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useRowListener('pets', 'fido', () => console.log('Row changed'));
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Row changed'
root.unmount();
console.log(store.getListenerStats().row);
// -> 0
useSetPartialRowCallback
The useSetPartialRowCallback hook returns a parameterized callback that can be used to set partial data of a single Row in the Store, leaving other Cell values unaffected.
useSetPartialRowCallback<Parameter>(
tableId: string,
rowId: string,
getPartialRow: (parameter: Parameter, store: Store) => Row,
getPartialRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, partialRow: Row) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
getPartialRow | (parameter: Parameter, store: Store) => Row | A function which returns the partial |
getPartialRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialRow: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the partial Row object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetPartialRowCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useSetPartialRowCallback(
'pets',
'nemo',
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, partialRow) =>
console.log(`Updated: ${JSON.stringify(partialRow)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
useSetRowCallback
The useSetRowCallback hook returns a parameterized callback that can be used to set the data of a single Row in a Store.
useSetRowCallback<Parameter>(
tableId: string,
rowId: string,
getRow: (parameter: Parameter, store: Store) => Row,
getRowDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, row: Row) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, row: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the Row object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetRowCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
const handleClick = useSetRowCallback(
'pets',
'nemo',
(e) => ({species: 'fish', bubbles: e.bubbles}),
[],
store,
(store, row) => console.log(`Updated: ${JSON.stringify(row)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"species":"fish","bubbles":true}'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
useCellIds
The useCellIds hook returns the Ids of every Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useCellIds(
tableId: string,
rowId: string,
storeOrStoreId?: StoreOrStoreId,
): Ids| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCellIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useCellIds hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido', store))}</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
store.setCell('pets', 'fido', 'species', 'dog');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useCellIds hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCellIds hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useCellIds('pets', 'fido', 'petStore'))}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
useCellIdsListener
The useCellIdsListener hook registers a listener function with a Store that will be called whenever the Cell Ids in a Row change.
useCellIdsListener(
tableId: IdOrNull,
rowId: IdOrNull,
listener: CellIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCellIds hook).
You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).
Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.
Unlike the addCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useCellIdsListener('pets', 'fido', () =>
console.log('Cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Cell Ids changed'
root.unmount();
console.log(store.getListenerStats().cellIds);
// -> 0
useCell
The useCell hook returns an object containing the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
useCell(
tableId: string,
rowId: string,
cellId: string,
storeOrStoreId?: StoreOrStoreId,
): CellOrUndefined| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | CellOrUndefined | The value of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCell hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useCell hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useCell('pets', 'fido', 'color', store)}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useCell hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{useCell('pets', 'fido', 'color')}</span>;
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCell hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useCell('pets', 'fido', 'color', 'petStore')}</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
useCellListener
The useCellListener hook registers a listener function with a Store that will be called whenever data in a Cell changes.
useCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: CellListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCell hook).
You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).
All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.
Unlike the addCellListener method, which returns a listener Id and requires you to remove it manually, the useCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useCellListener('pets', 'fido', 'color', () =>
console.log('Cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Cell changed'
root.unmount();
console.log(store.getListenerStats().cell);
// -> 0
useDelCellCallback
The useDelCellCallback hook returns a callback that can be used to remove a single Cell from a Row.
useDelCellCallback(
tableId: string,
rowId: string,
cellId: string,
forceDel?: boolean,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelCellCallback hook to create an event handler which deletes from the Store when the span element is clicked.
const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
const handleClick = useDelCellCallback(
'pets',
'nemo',
'species',
false,
store,
() => console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useTables(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
useSetCellCallback
The useSetCellCallback hook returns a parameterized callback that can be used to set the value of a single Cell in a Store.
useSetCellCallback<Parameter>(
tableId: string,
rowId: string,
cellId: string,
getCell: (parameter: Parameter, store: Store) => Cell | MapCell,
getCellDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, cell: Cell | MapCell) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
getCell | (parameter: Parameter, store: Store) => Cell | MapCell | A function which returns the |
getCellDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, cell: Cell | MapCell) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The fourth parameter is a function which will produce the Cell object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Examples
This example uses the useSetCellCallback hook to create an event handler which updates the Store with a Cell value when the span element is clicked.
const store = createStore().setCell('pets', 'nemo', 'species', 'fish');
const App = () => {
const handleClick = useSetCellCallback(
'pets',
'nemo',
'bubbles',
(e) => e.bubbles,
[],
store,
(store, cell) => console.log(`Updated: ${cell}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'
console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
This example uses the useSetCellCallback hook to create an event handler which updates the Store via a MapCell function when the span element is clicked.
const store = createStore().setCell('pets', 'nemo', 'visits', 1);
const App = () => {
const handleClick = useSetCellCallback(
'pets',
'nemo',
'visits',
(e) => (visits) => visits + (e.bubbles ? 1 : 0),
[],
store,
(store, cell) => console.log(`Updated with MapCell function`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useRow('pets', 'nemo', store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"visits":1}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated with MapCell function'
console.log(span.innerHTML);
// -> '{"visits":2}'
useDelValuesCallback
The useDelValuesCallback hook returns a callback that can be used to remove all of the keyed value data in a Store.
useDelValuesCallback(
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelValuesCallback hook to create an event handler which deletes from the Store when the span element is clicked.
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useDelValuesCallback(store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{}'
Since
v3.0.0
useSetPartialValuesCallback
The useSetPartialValuesCallback hook returns a parameterized callback that can be used to set partial Values data in the Store, leaving other Values unaffected.
useSetPartialValuesCallback<Parameter>(
getPartialValues: (parameter: Parameter, store: Store) => Values,
getPartialValuesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, partialValues: Values) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getPartialValues | (parameter: Parameter, store: Store) => Values | A function which returns the partial |
getPartialValuesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialValues: Values) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The third parameter is a function which will produce the partial Values object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetPartialValuesCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useSetPartialValuesCallback(
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, partialValues) =>
console.log(`Updated: ${JSON.stringify(partialValues)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"open":true,"bubbles":true}'
Since
v3.0.0
useSetValuesCallback
The useSetValuesCallback hook returns a parameterized callback that can be used to set the keyed value data of a Store.
useSetValuesCallback<Parameter>(
getValues: (parameter: Parameter, store: Store) => Values,
getValuesDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, values: Values) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
getValues | (parameter: Parameter, store: Store) => Values | A function which returns the |
getValuesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, values: Values) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The first parameter is a function which will produce the Values object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetValuesCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setValues({open: true});
const App = () => {
const handleClick = useSetValuesCallback(
(e) => ({bubbles: e.bubbles}),
[],
store,
(store, values) => console.log(`Updated: ${JSON.stringify(values)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'
console.log(span.innerHTML);
// -> '{"bubbles":true}'
Since
v3.0.0
useValues
The useValues hook returns a Values object containing the keyed value data of a Store, and registers a listener so that any changes to that result will cause a re-render.
useValues(storeOrStoreId?: StoreOrStoreId): Values| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Values | A |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValues hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Values will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValues hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValues(store))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>{"open":false}</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValues hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValues())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValues hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValues('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>{"open":true}</span>'
Since
v3.0.0
useValuesListener
The useValuesListener hook registers a listener function with a Store that will be called whenever keyed value data in it changes.
useValuesListener(
listener: ValuesListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | ValuesListener | The function that will be called whenever keyed value data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValues hook).
Unlike the addValuesListener method, which returns a listener Id and requires you to remove it manually, the useValuesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useValuesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValuesListener(() => console.log('Values changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().values);
// -> 1
store.setValue('open', false);
// -> 'Values changed'
root.unmount();
console.log(store.getListenerStats().values);
// -> 0
Since
v3.0.0
useDelValueCallback
The useDelValueCallback hook returns a callback that can be used to remove a single Value from a Store.
useDelValueCallback(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store) => void,
thenDeps?: DependencyList,
): Callback| Type | Description | |
|---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | Callback | A callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will delete data in a Store.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.
The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useDelValueCallback hook to create an event handler which deletes from the Store when the span element is clicked.
const store = createStore().setValues({open: true, employees: 3});
const App = () => {
const handleClick = useDelValueCallback('open', store, () =>
console.log('Deleted'),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true,"employees":3}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'
console.log(span.innerHTML);
// -> '{"employees":3}'
Since
v3.0.0
useSetValueCallback
The useSetValueCallback hook returns a parameterized callback that can be used to set the data of a single Value in a Store.
useSetValueCallback<Parameter>(
valueId: string,
getValue: (parameter: Parameter, store: Store) => Value | MapValue,
getValueDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
then?: (store: Store, value: Value | MapValue) => void,
thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>| Type | Description | |
|---|---|---|
valueId | string | |
getValue | (parameter: Parameter, store: Store) => Value | MapValue | A function which returns the |
getValueDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, value: Value | MapValue) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
| returns | ParameterizedCallback<Parameter> | A parameterized callback for subsequent use. |
This hook is useful, for example, when creating an event handler that will mutate the data in the Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.
The second parameter is a function which will produce the Value object that will then be used to update the Store in the callback.
If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.
For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.
The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.
Example
This example uses the useSetValueCallback hook to create an event handler which updates the Store when the span element is clicked.
const store = createStore().setValue('open', true);
const App = () => {
const handleClick = useSetValueCallback(
'bubbles',
(e) => e.bubbles,
[],
store,
(store, value) => console.log(`Updated: ${JSON.stringify(value)}`),
);
return (
<span id="span" onClick={handleClick}>
{JSON.stringify(useValues(store))}
</span>
);
};
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"open":true}'
// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'
console.log(span.innerHTML);
// -> '{"open":true,"bubbles":true}'
Since
v3.0.0
useValue
The useValue hook returns an object containing the data of a single Value in a Store, and registers a listener so that any changes to that result will cause a re-render.
useValue(
valueId: string,
storeOrStoreId?: StoreOrStoreId,
): ValueOrUndefined| Type | Description | |
|---|---|---|
valueId | string | |
storeOrStoreId? | StoreOrStoreId | The |
| returns | ValueOrUndefined | An object containing the entire data of the |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValue hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValue hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValue('open', store))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValue hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValue('open'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValue hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useValue('open', 'petStore'))}</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>true</span>'
Since
v3.0.0
useValueIds
The useValueIds hook returns the Ids of every Value in a Store, and registers a listener so that any changes to that result will cause a re-render.
useValueIds(storeOrStoreId?: StoreOrStoreId): Ids| Type | Description | |
|---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
| returns | Ids |
A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useValueIds hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.
When first rendered, this hook will create a listener so that changes to the Value Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Store outside the application, which is used in the useValueIds hook by reference. A change to the data in the Store re-renders the component.
const store = createStore().setValue('open', true);
const App = () => <span>{JSON.stringify(useValueIds(store))}</span>;
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<span>["open","employees"]</span>'
This example creates a Provider context into which a default Store is provided. A component within it then uses the useValueIds hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValueIds())}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useValueIds hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useValueIds('petStore'))}</span>;
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>["open"]</span>'
Since
v3.0.0
useValueIdsListener
The useValueIdsListener hook registers a listener function with a Store that will be called whenever the Value Ids in it change.
useValueIdsListener(
listener: ValueIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | ValueIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValueIds hook).
Unlike the addValueIdsListener method, which returns a listener Id and requires you to remove it manually, the useValueIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useValueIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValueIdsListener(() => console.log('Value Ids changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().valueIds);
// -> 1
store.setValue('employees', 3);
// -> 'Value Ids changed'
root.unmount();
console.log(store.getListenerStats().valueIds);
// -> 0
Since
v3.0.0
useValueListener
The useValueListener hook registers a listener function with a Store that will be called whenever data in a Value changes.
useValueListener(
valueId: IdOrNull,
listener: ValueListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
valueId | IdOrNull | |
listener | ValueListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useValue hook).
You can either listen to a single Value (by specifying its Id as the method's first parameter) or changes to any Value (by providing a null wildcard).
Unlike the addValueListener method, which returns a listener Id and requires you to remove it manually, the useValueListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useValueListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useValueListener('open', () => console.log('Value changed'));
return <span>App</span>;
};
const store = createStore().setValues({open: true});
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().value);
// -> 1
store.setValue('open', false);
// -> 'Value changed'
root.unmount();
console.log(store.getListenerStats().value);
// -> 0
Since
v3.0.0
useDidFinishTransactionListener
The useDidFinishTransactionListener hook registers a listener function with a Store that will be called just after other non-mutating listeners are called at the end of the transaction.
useDidFinishTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called after the end of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Unlike the addDidFinishTransactionListener method, which returns a listener Id and requires you to remove it manually, the useDidFinishTransactionListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useDidFinishTransactionListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useDidFinishTransactionListener(
() => console.log('Did finish transaction'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Did finish transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
useStartTransactionListener
The useStartTransactionListener hook registers a listener function with the Store that will be called at the start of a transaction.
useStartTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called at the start of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Unlike the addStartTransactionListener method, which returns a listener Id and requires you to remove it manually, the useStartTransactionListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useStartTransactionListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useStartTransactionListener(() => console.log('Start transaction'));
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Start transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
useWillFinishTransactionListener
The useWillFinishTransactionListener hook registers a listener function with a Store that will be called just before other non-mutating listeners are called at the end of the transaction.
useWillFinishTransactionListener(
listener: TransactionListener,
listenerDeps?: DependencyList,
storeOrStoreId?: StoreOrStoreId,
): void| Type | Description | |
|---|---|---|
listener | TransactionListener | The function that will be called before the end of a transaction. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
| returns | void | This has no return value. |
Unlike the addWillFinisTransactionListener method, which returns a listener Id and requires you to remove it manually, the useWillFinishTransactionListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.
Example
This example uses the useWillFinishTransactionListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => {
useWillFinishTransactionListener(
() => console.log('Will finish transaction'),
);
return <span>App</span>;
};
const store = createStore();
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} />);
console.log(store.getListenerStats().transaction);
// -> 1
store.setValue('open', false);
// -> 'Will finish transaction'
root.unmount();
console.log(store.getListenerStats().transaction);
// -> 0
Since
v4.2.2
Checkpoints components
This is the collection of checkpoints components within the ui-react module. There are 4 checkpoints components in total.
BackwardCheckpointsView
The BackwardCheckpointsView component renders a list of previous checkpoints that the underlying Store can go back to.
BackwardCheckpointsView(props: BackwardCheckpointsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | BackwardCheckpointsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the previous checkpoints, if present. |
The component's props identify which previous checkpoints to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.
This component uses the useCheckpointIds hook under the covers, which means that any changes to the checkpoint Ids in the Checkpoints object will cause a re-render.
Examples
This example creates a Checkpoints object outside the application, which is used in the BackwardCheckpointsView component by reference to render a list of previous checkpoints.
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<BackwardCheckpointsView checkpoints={checkpoints} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>initial</div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>initial/identified</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The BackwardCheckpointsView component within it then renders the list of previous checkpoints (with Ids for readability).
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<BackwardCheckpointsView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>0:{initial}1:{identified}</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The BackwardCheckpointsView component within it then renders the list of previous checkpoints with a custom Row component and a custom props callback.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<BackwardCheckpointsView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={(checkpointId) => ({
bold: checkpointId == '0',
})}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>0</b>: initial</span><span>1: identified</span></div>'
CheckpointView
The CheckpointView component simply renders the label of a checkpoint.
CheckpointView(props: CheckpointProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | CheckpointProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the checkpoint: its label if present, or |
The component's props identify which checkpoint to render based on Checkpoint Id and Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
The primary purpose of this component is to render multiple checkpoints in a BackwardCheckpointsView component or ForwardCheckpointsView component.
This component uses the useCheckpoint hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.
Example
This example creates a Checkpoints object outside the application, which is used in the CheckpointView component by reference to render a checkpoint with a label (with its Id for readability).
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<CheckpointView
checkpointId="1"
checkpoints={checkpoints}
debugIds={true}
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>1:{}</div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>1:{sale}</div>'
checkpoints.setCheckpoint('1', 'sold');
console.log(app.innerHTML);
// -> '<div>1:{sold}</div>'
CurrentCheckpointView
The CurrentCheckpointView component renders the current checkpoint that the underlying Store is currently on.
CurrentCheckpointView(props: CurrentCheckpointProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | CurrentCheckpointProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the current checkpoint, if present. |
The component's props identify which current checkpoint to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
By default the current checkpoint is rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.
This component uses the useCheckpointIds hook under the covers, which means that any changes to the current checkpoint Id in the Checkpoints object will cause a re-render.
Examples
This example creates a Checkpoints object outside the application, which is used in the CurrentCheckpointView component by reference to render the current checkpoints.
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<CurrentCheckpointView checkpoints={checkpoints} />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>identified</div>'
store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<div></div>'
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>sale</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The CurrentCheckpointView component within it then renders current checkpoint (with its Id for readability).
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<CurrentCheckpointView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>1:{identified}</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The CurrentCheckpointView component within it then renders the list of future checkpoints with a custom Row component and a custom props callback.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<CurrentCheckpointView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={(checkpointId) => ({
bold: checkpointId == '1',
})}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span></div>'
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div><span>2: sale</span></div>'
ForwardCheckpointsView
The ForwardCheckpointsView component renders a list of future checkpoints that the underlying Store can go forwards to.
ForwardCheckpointsView(props: ForwardCheckpointsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ForwardCheckpointsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the future checkpoints, if present. |
The component's props identify which future checkpoints to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).
This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.
This component uses the useCheckpointIds hook under the covers, which means that any changes to the checkpoint Ids in the Checkpoints object will cause a re-render.
Examples
This example creates a Checkpoints object outside the application, which is used in the ForwardCheckpointsView component by reference to render a list of future checkpoints.
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
<div>
<ForwardCheckpointsView checkpoints={checkpoints} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div></div>'
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>sale</div>'
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>identified/sale</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The ForwardCheckpointsView component within it then renders the list of future checkpoints (with Ids for readability).
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ForwardCheckpointsView debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div>1:{identified}2:{sale}</div>'
This example creates a Provider context into which a default Checkpoints object is provided. The ForwardCheckpointsView component within it then renders the list of future checkpoints with a custom Row component and a custom props callback.
const App = ({checkpoints}) => (
<Provider checkpoints={checkpoints}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ForwardCheckpointsView
checkpointComponent={FormattedCheckpointView}
getCheckpointComponentProps={(checkpointId) => ({
bold: checkpointId == '1',
})}
/>
</div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
<span>
{bold ? <b>{checkpointId}</b> : checkpointId}
{': '}
<CheckpointView
checkpoints={checkpoints}
checkpointId={checkpointId}
/>
</span>
);
const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App checkpoints={checkpoints} />);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span><span>2: sale</span></div>'
Context components
This is the collection of context components within the ui-react module. There is only one function, Provider.
Provider
The Provider component is used to wrap part of an application in a context that provides default objects to be used by hooks and components within.
Provider(props: ProviderProps & {children: ReactNode}): ComponentReturnType| Type | Description | |
|---|---|---|
props | ProviderProps & {children: ReactNode} | The props for this component. |
| returns | ComponentReturnType | A rendering of the child components. |
Store, Metrics, Indexes, Relationships, Queries, and Checkpoints objects can be passed into the context of an application and used throughout. One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.
Provider contexts can be nested and the objects passed in will be merged. For example, if an outer context contains a default Metrics object and an inner context contains only a default Store, both the Metrics objects and the Store will be visible within the inner context. If the outer context contains a Store named by Id and the inner context contains a Store named by a different Id, both will be visible within the inner context.
Examples
This example creates a Provider context into which a Store and a Metrics object are provided, one by default, and one named by Id. Components within it then render content from both, without the need to have them passed as props.
const App = ({store, metrics}) => (
<Provider store={store} metricsById={{petStore: metrics}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" />,
<CellView tableId="species" rowId="cat" cellId="price" />,
{useMetric('highestPrice', 'petStore')}
</span>
);
const store = createStore();
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App store={store} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,4,5</span>'
This example creates nested Provider contexts into which Store and Metrics objects are provided, showing how visibility is merged.
const App = ({petStore, metrics}) => (
<Provider storesById={{pet: petStore}} metrics={metrics}>
<OuterPane />
</Provider>
);
const OuterPane = () => {
const planetStore = useCreateStore(() =>
createStore().setTables({planets: {mars: {moons: 2}}}),
);
return (
<Provider storesById={{planet: planetStore}}>
<InnerPane />
</Provider>
);
};
const InnerPane = () => (
<span>
<CellView tableId="species" rowId="dog" cellId="price" store="pet" />,
{useMetric('highestPrice')},
<CellView
tableId="planets"
rowId="mars"
cellId="moons"
store="planet"
/>
</span>
);
const petStore = createStore();
petStore.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(petStore);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App petStore={petStore} metrics={metrics} />);
console.log(app.innerHTML);
// -> '<span>5,5,2</span>'
Indexes components
This is the collection of indexes components within the ui-react module. There are only two indexes components, IndexView and SliceView.
IndexView
The IndexView component renders the contents of a Index, and registers a listener so that any changes to that result will cause a re-render.
IndexView(props: IndexProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | IndexProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Index to render based on Index Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).
This component renders a Index by iterating over its Slice objects. By default these are in turn rendered with the SliceView component, but you can override this behavior by providing a sliceComponent prop, a custom component of your own that will render a Slice based on SliceProps. You can also pass additional props to your custom component with the getSliceComponentProps callback prop.
This component uses the useSliceIds hook under the covers, which means that any changes to the structure of the Index will cause a re-render.
Examples
This example creates an Indexes object outside the application, which is used in the IndexView component by reference. A change to the Slice Ids re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<div>
<IndexView indexId="bySpecies" indexes={indexes} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'
store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<div>dog/cat/worm</div>'
This example creates a Provider context into which a default Indexes object is provided. The IndexView component within it then renders the Index (with Ids for readability).
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<IndexView indexId="bySpecies" debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div>bySpecies:{dog:{fido:{species:{dog}}cujo:{species:{dog}}}}</div>'
This example creates a Provider context into which a default Indexes object is provided. The IndexView component within it then renders the Index with a custom Slice component and a custom props callback.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<IndexView
indexId="bySpecies"
sliceComponent={FormattedSliceView}
getSliceComponentProps={(sliceId) => ({bold: sliceId == 'dog'})}
/>
</div>
);
const FormattedSliceView = ({indexId, sliceId, bold}) => (
<span>
{bold ? <b>{sliceId}</b> : sliceId}
{': '}
<SliceView indexId={indexId} sliceId={sliceId} separator="/" />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: dog/dog</span><span>cat: cat</span></div>'
SliceView
The SliceView component renders the contents of a Slice, and registers a listener so that any changes to that result will cause a re-render.
SliceView(props: SliceProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SliceProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Slice to render based on Index Id, Slice Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).
This component renders a Slice by iterating over its Row objects. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useSliceRowIds hook under the covers, which means that any changes to the structure of the Slice will cause a re-render.
Examples
This example creates an Indexes object outside the application, which is used in the SliceView component by reference. A change to the Row Ids re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
<div>
<SliceView
indexId="bySpecies"
sliceId="dog"
indexes={indexes}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setRow('pets', 'cujo', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'
This example creates a Provider context into which a default Indexes object is provided. The SliceView component within it then renders the Slice (with Ids for readability).
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<SliceView indexId="bySpecies" sliceId="dog" debugIds={true} />
</div>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'
This example creates a Provider context into which a default Indexes object is provided. The SliceView component within it then renders the Slice with a custom Row component and a custom props callback.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<SliceView
indexId="bySpecies"
sliceId="dog"
rowComponent={FormattedRowView}
getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} separator="/" />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog/brown</span><span>cujo: dog</span></div>'
Metrics components
This is the collection of metrics components within the ui-react module. There is only one function, MetricView.
MetricView
The MetricView component renders the current value of a Metric, and registers a listener so that any changes to that result will cause a re-render.
MetricView(props: MetricProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | MetricProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props can identify which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or by explicit reference.
This component uses the useMetric hook under the covers, which means that any changes to the Metric will cause a re-render.
Examples
This example creates a Metrics object outside the application, which is used in the MetricView component hook by reference. A change to the Metric re-renders the component.
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => (
<div>
<MetricView metricId="highestPrice" metrics={metrics} />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>5</div>'
store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<div>20</div>'
This example creates a Provider context into which a default Metrics object is provided. The MetricView component within it then renders the Metric (with its Id for readability).
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<MetricView metricId="highestPrice" debugIds={true} />
</div>
);
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<div>highestPrice:{5}</div>'
This example creates a Provider context into which a default Metrics object is provided. The MetricView component within it then attempts to render a non-existent Metric.
const App = ({metrics}) => (
<Provider metrics={metrics}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<MetricView metricId="lowestPrice" />
</div>
);
const store = createStore().setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App metrics={metrics} />);
console.log(app.innerHTML);
// -> '<div></div>'
Queries components
This is the collection of queries components within the ui-react module. There are 4 queries components in total.
ResultSortedTableView
The ResultSortedTableView component renders the contents of a single query's sorted ResultTable in a Queries object, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableView(props: ResultSortedTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultSortedTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a ResultTable by iterating over its Row objects, in the order dictated by the sort parameters. By default these are in turn rendered with the ResultRowView component, but you can override this behavior by providing a resultRowComponent prop, a custom component of your own that will render a Row based on ResultRowProps. You can also pass additional props to your custom component with the getResultRowComponentProps callback prop.
This component uses the useResultSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the ResultTable will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultSortedTableView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>black/brown</div>'
store.setCell('pets', 'felix', 'color', 'white');
console.log(app.innerHTML);
// -> '<div>brown/white</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableView component within it then renders the Table (with Ids for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
debugIds={true}
/>
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>petColors:{felix:{color:{black}}fido:{color:{brown}}}</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableView component within it then renders the Table with a custom Row component and a custom props callback.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span>felix: black</span><span><b>fido</b>: brown</span></div>'
Since
v2.0.0
ResultTableView
The ResultTableView component renders the contents of a single query's ResultTable in a Queries object, and registers a listener so that any changes to that result will cause a re-render.
ResultTableView(props: ResultTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference).
This component renders a ResultTable by iterating over its Row objects. By default these are in turn rendered with the ResultRowView component, but you can override this behavior by providing a resultRowComponent prop, a custom component of your own that will render a Row based on ResultRowProps. You can also pass additional props to your custom component with the getResultRowComponentProps callback prop.
This component uses the useResultRowIds hook under the covers, which means that any changes to the structure of the ResultTable will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultTableView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultTableView queryId="petColors" queries={queries} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>brown/black</div>'
store.setRow('pets', 'cujo', {species: 'dog', color: 'black'});
console.log(app.innerHTML);
// -> '<div>brown/black/black</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultTableView component within it then renders the Table (with Ids for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultTableView queryId="petColors" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>petColors:{fido:{color:{brown}}felix:{color:{black}}}</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultTableView component within it then renders the Table with a custom Row component and a custom props callback.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultTableView
queryId="petColors"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: brown</span><span>felix: black</span></div>'
Since
v2.0.0
ResultRowView
The ResultRowView component renders the contents of a single Row in a given query's ResultTable, and registers a listener so that any changes to that result will cause a re-render.
ResultRowView(props: ResultRowProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultRowProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the result |
The component's props identify which Row to render based on query Id, Row Id, and Queries object (which is either the default context Queries object, a named context Queries object, or an explicit reference).
This component renders a Row by iterating over its Cell values. By default these are in turn rendered with the ResultCellView component, but you can override this behavior by providing a resultCellComponent prop, a custom component of your own that will render a Cell based on ResultCellProps. You can also pass additional props to your custom component with the getResultCellComponentProps callback prop.
You can create your own ResultRowView-like component to customize the way that a result Row is rendered: see the ResultTableView component for more details.
This component uses the useResultCellIds hook under the covers, which means that any changes to the structure of the result Row will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultRowView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => {
select('species');
select('color');
},
);
const App = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/brown</div>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultRowView component within it then renders the Row (with Ids for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultRowView queryId="petColors" rowId="fido" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div>fido:{species:{dog}color:{brown}}</div>'
This example creates a Provider context into which a default Queries object is provided. The ResultRowView component within it then renders the Row with a custom Cell component and a custom props callback.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
resultCellComponent={FormattedResultCellView}
getResultCellComponentProps={(cellId) => ({
bold: cellId == 'species',
})}
/>
</div>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: brown</span></div>'
Since
v2.0.0
ResultCellView
The ResultCellView component renders the value of a single Cell in a given Row, in a given query's ResultTable, and registers a listener so that any changes to that result will cause a re-render.
ResultCellView(props: ResultCellProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultCellProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the result |
The component's props identify which Cell to render based on query Id, Row Id, Cell Id, and Queries object (which is either the default context Queries object, a named context Queries object, or an explicit reference).
A Cell contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own ResultCellView-like component to customize the way that a Cell is rendered: see the ResultRowView component for more details.
This component uses the useResultCell hook under the covers, which means that any changes to the specified Cell will cause a re-render.
Examples
This example creates a Queries object outside the application, which is used in the ResultCellView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
queries={queries}
/>
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Queries object is provided. The ResultCellView component within it then renders the Cell (with its Id for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
debugIds={true}
/>
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Queries object is provided. The ResultCellView component within it then attempts to render a non-existent Cell.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView queryId="petColors" rowId="fido" cellId="height" />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v2.0.0
Relationships components
This is the collection of relationships components within the ui-react module. There are only three relationships components, LinkedRowsView, LocalRowsView, and RemoteRowView.
LinkedRowsView
The LinkedRowsView component renders the local Row objects for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
LinkedRowsView(props: LinkedRowsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | LinkedRowsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the local |
The component's props identify which local Rows to render based on Relationship Id, remote Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
By default the local Rows are rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useLocalRowIds hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.
Examples
This example creates a Relationships object outside the application, which is used in the LinkedRowsView component by reference. A change to the Row Ids re-renders the component.
const store = createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {next: 'cujo'},
cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSequence',
'pets',
'pets',
'next',
);
const App = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
relationships={relationships}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>felix/cujo/dog</div>'
store.setRow('pets', 'toto', {species: 'dog'});
store.setRow('pets', 'cujo', {next: 'toto'});
console.log(app.innerHTML);
// -> '<div>felix/cujo/toto/dog</div>'
This example creates a Provider context into which a default Relationships object is provided. The LinkedRowsView component within it then renders the local Row objects (with Ids for readability).
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {species: 'cat'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>fido:{fido:{next:{felix}}felix:{species:{cat}}}</div>'
This example creates a Provider context into which a default Relationships object is provided. The LinkedRowsView component within it then renders the local Row objects with a custom Row component and a custom props callback.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LinkedRowsView
relationshipId="petSequence"
firstRowId="fido"
rowComponent={FormattedRowView}
getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore().setTable('pets', {
fido: {next: 'felix'},
felix: {species: 'cat'},
}),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: felix</span><span>felix: cat</span></div>'
LocalRowsView
The LocalRowsView component renders the local Row objects for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
LocalRowsView(props: LocalRowsProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | LocalRowsProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the local |
The component's props identify which local Rows to render based on Relationship Id, remote Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
By default the local Rows are rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useLocalRowIds hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.
Examples
This example creates a Relationships object outside the application, which is used in the LocalRowsView component by reference. A change to the Row Ids re-renders the component.
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
relationships={relationships}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'
store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog/dog</div>'
This example creates a Provider context into which a default Relationships object is provided. The LocalRowsView component within it then renders the local Row objects (with Ids for readability).
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'
This example creates a Provider context into which a default Relationships object is provided. The LocalRowsView component within it then renders the local Row objects with a custom Row component and a custom props callback.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<LocalRowsView
relationshipId="petSpecies"
remoteRowId="dog"
rowComponent={FormattedRowView}
getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>cujo: dog</span></div>'
RemoteRowView
The RemoteRowView component renders the remote Row Id for a given local Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.
RemoteRowView(props: RemoteRowProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | RemoteRowProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the remote |
The component's props identify which remote Row to render based on Relationship Id, local Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
By default the remote Row is rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useRemoteRowId hook under the covers, which means that any changes to the remote Row Id in the Relationship will cause a re-render.
Examples
This example creates a Relationships object outside the application, which is used in the RemoteRowView component by reference. A change to the Row Ids re-renders the component.
const store = createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
'petSpecies',
'pets',
'species',
'species',
);
const App = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
relationships={relationships}
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>5</div>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<div>10</div>'
This example creates a Provider context into which a default Relationships object is provided. The RemoteRowView component within it then renders the remote Row (with Ids for readability).
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
debugIds={true}
/>
</div>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div>cujo:{dog:{price:{5}}}</div>'
This example creates a Provider context into which a default Relationships object is provided. The RemoteRowView component within it then renders the remote Row with a custom Row component and a custom props callback.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<RemoteRowView
relationshipId="petSpecies"
localRowId="cujo"
rowComponent={FormattedRowView}
getRowComponentProps={(rowId) => ({bold: rowId == 'dog'})}
/>
</div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView store={store} tableId={tableId} rowId={rowId} />
</span>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: 5</span></div>'
Store components
This is the collection of store components within the ui-react module. There are 7 store components in total.
TablesView
The TablesView component renders the tabular contents of a Store, and registers a listener so that any changes to that result will cause a re-render.
TablesView(props: TablesProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | TablesProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props can identify which Store to render - either the default context Store, a named context Store, or an explicit reference.
This component renders a Store by iterating over its Table objects. By default these are in turn rendered with the TableView component, but you can override this behavior by providing a tableComponent prop, a custom component of your own that will render a Table based on TableProps. You can also pass additional props to your custom component with the getTableComponentProps callback prop.
This component uses the useTableIds hook under the covers, which means that any changes to the structure of the Store will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the TablesView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const App = () => (
<div>
<TablesView store={store} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setTable('species', {dog: {price: 5}});
console.log(app.innerHTML);
// -> '<div>dog/5</div>'
This example creates a Provider context into which a default Store is provided. The TablesView component within it then renders the Store (with Ids for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<TablesView debugIds={true} />
</div>
);
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}}species:{dog:{price:{5}}}</div>'
This example creates a Provider context into which a default Store is provided. The TablesView component within it then renders the Store with a custom Table component and a custom props callback.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<TablesView
tableComponent={FormattedTableView}
getTableComponentProps={(tableId) => ({bold: tableId == 'pets'})}
/>
</div>
);
const FormattedTableView = ({tableId, bold}) => (
<span>
{bold ? <b>{tableId}</b> : tableId}
{': '}
<TableView tableId={tableId} />
</span>
);
const store = createStore().setTables({
pets: {fido: {species: 'dog'}},
species: {dog: {price: 5}},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>pets</b>: dog</span><span>species: 5</span></div>'
TableView
The TableView component renders the contents of a single Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
TableView(props: TableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | TableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference).
This component renders a Table by iterating over its Row objects. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
You can create your own TableView-like component to customize the way that a Table is rendered: see the TablesView component for more details.
This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.
Since v4.1.0, you can use the customCellIds prop if you want to render a prescribed set of the Table's Cells in a given order for each Row.
Examples
This example creates a Store outside the application, which is used in the TableView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const App = () => (
<div>
<TableView tableId="pets" store={store} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setRow('pets', 'felix', {species: 'cat'});
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'
This example creates a Provider context into which a default Store is provided. The TableView component within it then renders the Table for a custom set of Cell Ids (and rendered with Ids for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['species'];
const Pane = () => (
<div>
<TableView
tableId="pets"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setTable('pets', {
fido: {color: 'black', species: 'dog'},
felix: {color: 'brown', species: 'cat'},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}felix:{species:{cat}}}</div>'
This example creates a Provider context into which a default Store is provided. The TableView component within it then renders the Table with a custom Row component and a custom props callback.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<TableView
tableId="pets"
rowComponent={FormattedRowView}
getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView tableId={tableId} rowId={rowId} />
</span>
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>felix: cat</span></div>'
SortedTableView
The SortedTableView component renders the contents of a single sorted Table in a Store, and registers a listener so that any changes to that result will cause a re-render.
SortedTableView(props: SortedTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SortedTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a Table by iterating over its Row objects, in the order dictated by the sort parameters. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.
This component uses the useSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the Table will cause a re-render.
Since v4.1.0, you can use the customCellIds prop if you want to render a prescribed set of the Table's Cells in a given order for each Row.
Examples
This example creates a Store outside the application, which is used in the SortedTableView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
store={store}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>cat/dog</div>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<div>cat/dog/wolf</div>'
This example creates a Provider context into which a default Store is provided. The SortedTableView component within it then renders the Table for a custom set of Cell Ids (and rendered with Ids for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['species'];
const Pane = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setTables({
pets: {
fido: {color: 'black', species: 'dog'},
felix: {color: 'brown', species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>pets:{felix:{species:{cat}}fido:{species:{dog}}}</div>'
This example creates a Provider context into which a default Store is provided. The SortedTableView component within it then renders the Table with a custom Row component and a custom props callback.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
rowComponent={FormattedRowView}
getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<RowView tableId={tableId} rowId={rowId} />
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span>felix: cat</span><span><b>fido</b>: dog</span></div>'
Since
v2.0.0
RowView
The RowView component renders the contents of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.
RowView(props: RowProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | RowProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Row to render based on Table Id, Row Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
This component renders a Row by iterating over its Cell values. By default these are in turn rendered with the CellView component, but you can override this behavior by providing a cellComponent prop, a custom component of your own that will render a Cell based on CellProps. You can also pass additional props to your custom component with the getCellComponentProps callback prop.
You can create your own RowView-like component to customize the way that a Row is rendered: see the TableView component for more details.
Since v4.1.0, you can use the customCellIds prop if you want to render a prescribed set of the Row's Cells in a given order. Otherwise, this component uses the useCellIds hook under the covers, which means that any changes to the structure of the Row will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the RowView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setRow('pets', 'fido', {species: 'dog'});
const App = () => (
<div>
<RowView tableId="pets" rowId="fido" store={store} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>dog</div>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'
This example creates a Provider context into which a default Store is provided. The RowView component within it then renders the Row for a custom set of Cell Ids (and rendered with Ids for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const customCellIds = ['color', 'species'];
const Pane = () => (
<div>
<RowView
tableId="pets"
rowId="fido"
customCellIds={customCellIds}
debugIds={true}
/>
</div>
);
const store = createStore().setRow('pets', 'fido', {
species: 'dog',
color: 'walnut',
legs: 4,
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>fido:{color:{walnut}species:{dog}}</div>'
This example creates a Provider context into which a default Store is provided. The RowView component within it then renders the Row with a custom Cell component and a custom props callback.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<RowView
tableId="pets"
rowId="fido"
cellComponent={FormattedCellView}
getCellComponentProps={(cellId) => ({bold: cellId == 'species'})}
/>
</div>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</span>
);
const store = createStore().setRow('pets', 'fido', {
species: 'dog',
color: 'walnut',
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: walnut</span></div>'
CellView
The CellView component renders the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.
CellView(props: CellProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | CellProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Cell to render based on Table Id, Row Id, Cell Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Cell contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own CellView-like component to customize the way that a Cell is rendered: see the RowView component for more details.
This component uses the useCell hook under the covers, which means that any changes to the specified Cell will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the CellView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="color" store={store} />
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>brown</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'
This example creates a Provider context into which a default Store is provided. The CellView component within it then renders the Cell (with its Id for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="color" debugIds={true} />
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Store is provided. The CellView component within it then attempts to render a non-existent Cell.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<CellView tableId="pets" rowId="fido" cellId="height" />
</span>
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span></span>'
ValuesView
The ValuesView component renders the keyed value contents of a Store, and registers a listener so that any changes to that result will cause a re-render.
ValuesView(props: ValuesProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValuesProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props can identify which Store to render - either the default context Store, a named context Store, or an explicit reference.
This component renders a Store by iterating over its Value objects. By default these are in turn rendered with the ValueView component, but you can override this behavior by providing a valueComponent prop, a custom component of your own that will render a Value based on ValueProps. You can also pass additional props to your custom component with the getValueComponentProps callback prop.
This component uses the useValueIds hook under the covers, which means that any changes to the Values in the Store will cause a re-render.
This component uses the useValueIds hook under the covers, which means that any changes to the Store's Values will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the ValuesView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setValue('open', true);
const App = () => (
<div>
<ValuesView store={store} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<div>true</div>'
store.setValue('employees', 3);
console.log(app.innerHTML);
// -> '<div>true/3</div>'
This example creates a Provider context into which a default Store is provided. The ValuesView component within it then renders the Values (with Ids for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ValuesView debugIds={true} />
</div>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div>open:{true}employees:{3}</div>'
This example creates a Provider context into which a default Store is provided. The ValuesView component within it then renders the Values with a custom Value component and a custom props callback.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ValuesView
valueComponent={FormattedValueView}
getValueComponentProps={(valueId) => ({bold: valueId == 'open'})}
/>
</div>
);
const FormattedValueView = ({valueId, bold}) => (
<span>
{bold ? <b>{valueId}</b> : valueId}
{': '}
<ValueView valueId={valueId} />
</span>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<div><span><b>open</b>: true</span><span>employees: 3</span></div>'
Since
v3.0.0
ValueView
The ValueView component renders the value of a single Value, and registers a listener so that any changes to that result will cause a re-render.
ValueView(props: ValueProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValueProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
The component's props identify which Value to render based on Value Id and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Value contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own ValueView-like component to customize the way that a Value is rendered: see the ValuesView component for more details.
This component uses the useValue hook under the covers, which means that any changes to the specified Value will cause a re-render.
Examples
This example creates a Store outside the application, which is used in the ValueView component by reference. A change to the data in the Store re-renders the component.
const store = createStore().setValue('open', true);
const App = () => (
<span>
<ValueView valueId="open" store={store} />
</span>
);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App />);
console.log(app.innerHTML);
// -> '<span>true</span>'
store.setValue('open', false);
console.log(app.innerHTML);
// -> '<span>false</span>'
This example creates a Provider context into which a default Store is provided. The ValueView component within it then renders the Value (with its Id for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ValueView valueId="open" debugIds={true} />
</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span>open:{true}</span>'
This example creates a Provider context into which a default Store is provided. The ValueView component within it then attempts to render a non-existent Value.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ValueView valueId="website" />
</span>
);
const store = createStore().setValue('open', true);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v3.0.0
Type Aliases
These are the type aliases within the ui-react module.
Checkpoints type aliases
This is the collection of checkpoints type aliases within the ui-react module. There is only one type alias, UndoOrRedoInformation.
UndoOrRedoInformation
The UndoOrRedoInformation type is an array that you can fetch from a Checkpoints object to that indicates if and how you can move the state of the underlying Store forward or backward.
[boolean, Callback, Id | undefined, string]This type is useful if you are building undo or redo buttons. See the useUndoInformation hook and the useRedoInformation hook for more details and examples.
Component type aliases
This is the collection of component type aliases within the ui-react module. There is only one type alias, ComponentReturnType.
ComponentReturnType
ComponentReturnType is a simple alias for what a React component can return: either a ReactElement, or null for an empty component.
ReactElement<any, any> | nullIdentity type aliases
This is the collection of identity type aliases within the ui-react module. There are 6 identity type aliases in total.
StoreOrStoreId
The StoreOrStoreId type is used when you need to refer to a Store in a React hook or component.
Store | IdIn some simple cases you will already have a direct reference to the Store.
This module also includes a Provider component that can be used to wrap multiple Store objects into a context that can be used throughout the app. In this case you will want to refer to a Store by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
CheckpointsOrCheckpointsId
The CheckpointsOrCheckpointsId type is used when you need to refer to a Checkpoints object in a React hook or component.
Checkpoints | IdIn some simple cases you will already have a direct reference to the Checkpoints object.
This module also includes a Provider component that can be used to wrap multiple Checkpoints objects into a context that can be used throughout the app. In this case you will want to refer to a Checkpoints object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
IndexesOrIndexesId
The IndexesOrIndexesId type is used when you need to refer to an Indexes object in a React hook or component.
Indexes | IdIn some simple cases you will already have a direct reference to the Indexes object.
This module also includes a Provider component that can be used to wrap multiple Indexes objects into a context that can be used throughout the app. In this case you will want to refer to an Indexes object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
MetricsOrMetricsId
The MetricsOrMetricsId type is used when you need to refer to a Metrics object in a React hook or component.
Metrics | IdIn some simple cases you will already have a direct reference to the Metrics object.
This module also includes a Provider component that can be used to wrap multiple Metrics objects into a context that can be used throughout the app. In this case you will want to refer to a Metrics object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
QueriesOrQueriesId
The QueriesOrQueriesId type is used when you need to refer to a Queries object in a React hook or component.
Queries | IdIn some simple cases you will already have a direct reference to the Queries object.
This module also includes a Provider component that can be used to wrap multiple Queries objects into a context that can be used throughout the app. In this case you will want to refer to a Queries object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
Since
v2.0.0
RelationshipsOrRelationshipsId
The RelationshipsOrRelationshipsId type is used when you need to refer to a Relationships object in a React hook or component.
Relationships | IdIn some simple cases you will already have a direct reference to the Relationships object.
This module also includes a Provider component that can be used to wrap multiple Relationships objects into a context that can be used throughout the app. In this case you will want to refer to a Relationships object by its Id in that context.
Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.
Props type aliases
This is the collection of props type aliases within the ui-react module. There are 23 props type aliases in total.
TablesProps
TablesProps props are used for components that refer to all the Tables in a Store, such as the TablesView component.
{
store?: StoreOrStoreId;
tableComponent?: ComponentType<TableProps>;
getTableComponentProps?: (tableId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
store? | StoreOrStoreId | The |
tableComponent? | ComponentType<TableProps> | A component for rendering each |
getTableComponentProps? | getTableComponentProps(tableId: string): ExtraProps | A custom function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
TableProps
TableProps props are used for components that refer to a single Table in a Store, such as the TableView component.
{
tableId: Id;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
SortedTableProps
SortedTableProps props are used for components that refer to a single sorted Table in a Store, such as the SortedTableView component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
RowProps
RowProps props are used for components that refer to a single Row in a Table, such as the RowView component.
{
tableId: Id;
rowId: Id;
store?: StoreOrStoreId;
cellComponent?: ComponentType<CellProps>;
getCellComponentProps?: (cellId: string): ExtraProps;
customCellIds?: Ids;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
tableId | Id | |
rowId | Id | |
store? | StoreOrStoreId | The |
cellComponent? | ComponentType<CellProps> | A custom component for rendering each |
getCellComponentProps? | getCellComponentProps(cellId: string): ExtraProps | A function for generating extra props for each custom |
customCellIds? | Ids | An optional list of |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
CellProps
CellProps props are used for components that refer to a single Cell in a Row, such as the CellView component.
{
tableId: Id;
rowId: Id;
cellId: Id;
store?: StoreOrStoreId;
debugIds?: boolean;
}MetricProps
MetricProps props are used for components that refer to a single Metric in a Metrics object, such as the MetricView component.
{
metricId: Id;
metrics?: MetricsOrMetricsId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
metricId | Id | |
metrics? | MetricsOrMetricsId | The |
debugIds? | boolean | Whether the component should also render the |
IndexProps
IndexProps props are used for components that refer to a single Index in an Indexes object, such as the IndexView component.
{
indexId: Id;
indexes?: IndexesOrIndexesId;
sliceComponent?: ComponentType<SliceProps>;
getSliceComponentProps?: (sliceId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
indexId | Id | |
indexes? | IndexesOrIndexesId | The |
sliceComponent? | ComponentType<SliceProps> | |
getSliceComponentProps? | getSliceComponentProps(sliceId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
SliceProps
SliceProps props are used for components that refer to a single Slice in an Index object, such as the SliceView component.
{
indexId: Id;
sliceId: Id;
indexes?: IndexesOrIndexesId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
rowComponent? | ComponentType<RowProps> | |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
LocalRowsProps
LocalRowsProps props are used for components that refer to a single Relationship in a Relationships object, and where you want to render local Rows based on a remote Row, such as the LocalRowsView component.
{
relationshipId: Id;
remoteRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
remoteRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
RemoteRowProps
RemoteRowProps props are used for components that refer to a single Relationship in a Relationships object, and where you want to render a remote Row based on a local Row, such as in the RemoteRowView component.
{
relationshipId: Id;
localRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
localRowId | Id | |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
debugIds? | boolean | Whether the component should also render the |
LinkedRowsProps
LinkedRowsProps props are used for components that refer to a single Relationship in a Relationships object, and where you want to render a linked list of Rows starting from a first Row, such as the LinkedRowsView component.
{
relationshipId: Id;
firstRowId: Id;
relationships?: RelationshipsOrRelationshipsId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
firstRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
ResultTableProps
ResultTableProps props are used for components that refer to a single query ResultTable, such as the ResultTableView component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | getResultRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultSortedTableProps
ResultSortedTableProps props are used for components that refer to a single sorted query ResultTable, such as the ResultSortedTableView component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | getResultRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultRowProps
ResultRowProps props are used for components that refer to a single Row in a query ResultTable, such as the ResultRowView component.
{
queryId: Id;
rowId: Id;
queries?: QueriesOrQueriesId;
resultCellComponent?: ComponentType<ResultCellProps>;
getResultCellComponentProps?: (cellId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
rowId | Id | The |
queries? | QueriesOrQueriesId | The |
resultCellComponent? | ComponentType<ResultCellProps> | A custom component for rendering each |
getResultCellComponentProps? | getResultCellComponentProps(cellId: string): ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
ResultCellProps
ResultRowProps props are used for components that refer to a single Cell in a Row of a ResultTable, such as the ResultCellView component.
{
queryId: Id;
rowId: Id;
cellId: Id;
queries?: QueriesOrQueriesId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
rowId | Id | |
cellId | Id | |
queries? | QueriesOrQueriesId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
BackwardCheckpointsProps
BackwardCheckpointsProps props are used for components that refer to a list of previous checkpoints in a Checkpoints object, such as the BackwardCheckpointsView component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | getCheckpointComponentProps(checkpointId: string): ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
CurrentCheckpointProps
CurrentCheckpointsProps props are used for components that refer to the current checkpoints in a Checkpoints object, such as the BackwardCheckpointsView component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: string): ExtraProps;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | getCheckpointComponentProps(checkpointId: string): ExtraProps | A function for generating extra props for each checkpoint component based on its |
debugIds? | boolean | Whether the component should also render the |
ForwardCheckpointsProps
ForwardCheckpointsProps props are used for components that refer to a list of future checkpoints in a Checkpoints object, such as the ForwardCheckpointsView component.
{
checkpoints?: CheckpointsOrCheckpointsId;
checkpointComponent?: ComponentType<CheckpointProps>;
getCheckpointComponentProps?: (checkpointId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | getCheckpointComponentProps(checkpointId: string): ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
ProviderProps
ProviderProps props are used with the Provider component, so that Store Metrics, Indexes, Relationships, Queries, and Checkpoints objects can be passed into the context of an application and used throughout.
{
store?: Store;
storesById?: {[storeId: Id]: Store};
metrics?: Metrics;
metricsById?: {[metricsId: Id]: Metrics};
indexes?: Indexes;
indexesById?: {[indexesId: Id]: Indexes};
relationships?: Relationships;
relationshipsById?: {[relationshipsId: Id]: Relationships};
queries?: Queries;
queriesById?: {[queriesId: Id]: Queries};
checkpoints?: Checkpoints;
checkpointsById?: {[checkpointsId: Id]: Checkpoints};
}| Type | Description | |
|---|---|---|
store? | Store | A default single |
storesById? | {[storeId: Id]: Store} | An object containing multiple |
metrics? | Metrics | A default single |
metricsById? | {[metricsId: Id]: Metrics} | An object containing multiple |
indexes? | Indexes | A default single |
indexesById? | {[indexesId: Id]: Indexes} | An object containing multiple |
relationships? | Relationships | A default single |
relationshipsById? | {[relationshipsId: Id]: Relationships} | An object containing multiple |
queries? | Queries | A default single |
queriesById? | {[queriesId: Id]: Queries} | An object containing multiple |
checkpoints? | Checkpoints | A default single |
checkpointsById? | {[checkpointsId: Id]: Checkpoints} | An object containing multiple |
One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.
ExtraProps
The ExtraProps type represents a set of arbitrary additional props.
{[propName: string]: any}ValuesProps
ValuesProps props are used for components that refer to all the Values in a Store, such as the ValuesView component.
{
store?: StoreOrStoreId;
valueComponent?: ComponentType<ValueProps>;
getValueComponentProps?: (valueId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
store? | StoreOrStoreId | The |
valueComponent? | ComponentType<ValueProps> | A custom component for rendering each |
getValueComponentProps? | getValueComponentProps(valueId: string): ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
Since
v3.0.0
ValueProps
ValueProps props are used for components that refer to a single Value in a Row, such as the ValueView component.
{
valueId: Id;
store?: StoreOrStoreId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
valueId | Id | |
store? | StoreOrStoreId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v3.0.0
CheckpointProps
CheckpointProps props are used for components that refer to a single checkpoint in a Checkpoints object, such as the CheckpointView component.
{
checkpointId: Id;
checkpoints?: CheckpointsOrCheckpointsId;
debugIds?: boolean;
}| Type | Description | |
|---|---|---|
checkpointId | Id | The |
checkpoints? | CheckpointsOrCheckpointsId | The |
debugIds? | boolean | Whether the component should also render the |
ui-react-dom
The ui-react-dom module of the TinyBase project provides components to make it easy to create web-based reactive apps with Store objects.
The components in this module use the react-dom module and so are not appropriate for environments like React Native (although those in the lower-level ui-react module are).
See also
UI Components demos
Since
v4.1.0
Functions
These are the functions within the ui-react-dom module.
Development components
This is the collection of development components within the ui-react-dom module. There is only one function, StoreInspector.
StoreInspector
The StoreInspector component renders a tool which allows you to view and edit the content of a Store in a debug web environment.
StoreInspector(props: StoreInspectorProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | StoreInspectorProps | The props for this component. |
| returns | ComponentReturnType | The rendering of the inspector tool. |
See the <StoreInspector /> demo for this component in action.
The component displays a nub in the corner of the screen which you may then click to interact with all the Store objects in the Provider component context.
The component's props identify the nub's initial location and panel state, though subsequent user changes to that will be preserved on each reload.
Example
This example creates a Provider context into which a default Store is provided. The StoreInspector component within it then renders the inspector tool.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <StoreInspector />;
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
// ...
console.log(app.innerHTML.substring(0, 35));
// -> '<aside id="tinybaseStoreInspector">'
Since
v4.1.0
Indexes components
This is the collection of indexes components within the ui-react-dom module. There is only one function, SliceInHtmlTable.
SliceInHtmlTable
The SliceInHtmlTable component renders the contents of a Slice as an HTML
SliceInHtmlTable(props: SliceInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SliceInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <SliceInHtmlTable /> demo for this component in action.
The component's props identify which Slice to render based on Index Id, Slice Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).
This component renders a Slice by iterating over its Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
This component uses the useSliceRowIds hook under the covers, which means that any changes to the structure of the Slice will cause a re-render.
You can use the headerRow and idColumn props to control whether labels and Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Indexes object is provided. The SliceInHtmlTable component within it then renders the Slice in a <table> element with a CSS class.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<SliceInHtmlTable indexId="bySpecies" sliceId="dog" className="slice" />
);
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// ->
`
<table class=\"slice\">
<thead>
<tr>
<th>Id</th>
<th>species</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
<tr>
<th>cujo</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Indexes object is provided. The SliceInHtmlTable component within it then renders the Slice with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({indexes}) => (
<Provider indexes={indexes}>
<Pane />
</Provider>
);
const Pane = () => (
<SliceInHtmlTable
indexId="bySpecies"
sliceId="dog"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId, cellId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App indexes={indexes} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:</td>
</tr>
<tr>
<td>cujo:</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Queries components
This is the collection of queries components within the ui-react-dom module. There are only two queries components, ResultSortedTableInHtmlTable and ResultTableInHtmlTable.
ResultSortedTableInHtmlTable
The SortedTableInHtmlTable component renders the contents of a single query's sorted ResultTable in a Queries object as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableInHtmlTable(props: ResultSortedTableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultSortedTableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <ResultSortedTableInHtmlTable /> demo for this component in action.
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a ResultTable by iterating over its Row objects, in the order dictated by the sort parameters. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the ResultCustomCell type for more details.
This component uses the useSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the ResultTable will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
The sortOnClick prop makes the table's sorting interactive such that the user can click on a column heading to sort by that column. The style classes sorted and ascending (or descending) are added so that you can provide hints to the user how the sorting is being applied.
Provide a paginator component for the ResultTable with the paginator prop. Set to true to use the default SortedTablePaginator, or provide your own component that accepts SortedTablePaginatorProps.
Finally, the onChange prop lets you listen to a user's changes to the ResultTable's sorting or pagination.
Examples
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableInHtmlTable component within it then renders the ResultTable in a <table> element with a CSS class.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultSortedTableInHtmlTable
queryId="petColors"
cellId="color"
className="table"
/>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ color</th>
</tr>
</thead>
<tbody>
<tr>
<th>felix</th>
<td>black</td>
</tr>
<tr>
<th>fido</th>
<td>brown</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Queries object is provided. The ResultSortedTableInHtmlTable component within it then renders the ResultTable with a custom component and a custom props callback for the color Cell. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultSortedTableInHtmlTable
queryId="petColors"
cellId="color"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
color: {
component: FormattedResultCellView,
getComponentProps: (rowId, cellId) => ({bold: rowId == 'fido'}),
},
};
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td>felix:black</td>
</tr>
<tr>
<td><b>fido</b>:brown</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
ResultTableInHtmlTable
The ResultTableInHtmlTable component renders the contents of a single query's ResultTable in a Queries object as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ResultTableInHtmlTable(props: ResultTableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ResultTableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <ResultTableInHtmlTable /> demo for this component in action.
The component's props identify which ResultTable to render based on query Id, and Queries object (which is either the default context Queries object, a named context Queries object, or by explicit reference).
This component renders a ResultTable by iterating over its Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the ResultCustomCell type for more details.
This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Queries object is provided. The ResultTableInHtmlTable component within it then renders the ResultTable in a <table> element with a CSS class.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultTableInHtmlTable queryId="petColors" className="table" />
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>color</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<td>brown</td>
</tr>
<tr>
<th>felix</th>
<td>black</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Queries object is provided. The ResultTableInHtmlTable component within it then renders the ResultTable with a custom component and a custom props callback for the color Cell. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<ResultTableInHtmlTable
queryId="petColors"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
color: {
component: FormattedResultCellView,
getComponentProps: (rowId, cellId) => ({bold: rowId == 'fido'}),
},
};
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App queries={queries} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:brown</td>
</tr>
<tr>
<td>felix:black</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Relationships components
This is the collection of relationships components within the ui-react-dom module. There is only one function, RelationshipInHtmlTable.
RelationshipInHtmlTable
The RelationshipInHtmlTable component renders the contents of the two Tables linked by a Relationship as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
RelationshipInHtmlTable(props: RelationshipInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | RelationshipInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the two |
See the <RelationshipInHtmlTable /> demo for this component in action.
The component's props identify which Relationship to render based on Relationship Id and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).
This component renders the two Table objects by iterating over their related Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
Note the use of dotted 'tableId.cellId' string pairs when specifying custom rendering for the cells in this table, since Cells from both the relationship's 'local' and 'remote' Table objects can be rendered and need to be distinguished
This component uses the useRowIds and useRemoteRowId hooks under the covers, which means that any changes to the structure of either Table resulting in a change to the relationship will cause a re-render.
You can use the headerRow and idColumn props to control whether labels and Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Relationships object is provided. The RelationshipInHtmlTable component within it then renders the two Tables linked by a relationship in a <table> element with a CSS class. Note the dotted pairs that are used as column headings.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<RelationshipInHtmlTable
relationshipId="petSpecies"
className="relationship"
/>
);
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// ->
`
<table class=\"relationship\">
<thead>
<tr>
<th>pets.Id</th>
<th>species.Id</th>
<th>pets.species</th>
<th>species.price</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<th>dog</th>
<td>dog</td>
<td>5</td>
</tr>
<tr>
<th>cujo</th>
<th>dog</th>
<td>dog</td>
<td>5</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Relationships object is provided. The RelationshipInHtmlTable component within it then renders the two Tables linked by a relationship with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({relationships}) => (
<Provider relationships={relationships}>
<Pane />
</Provider>
);
const Pane = () => (
<RelationshipInHtmlTable
relationshipId="petSpecies"
customCells={customCells}
idColumn={false}
headerRow={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, store, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView
tableId={tableId}
rowId={rowId}
cellId={cellId}
store={store}
/>
</>
);
const customCells = {
'species.price': {
component: FormattedCellView,
getComponentProps: (rowId, cellId) => ({bold: rowId == 'dog'}),
},
};
const relationships = createRelationships(
createStore()
.setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'wolf'}})
.setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
const app = document.createElement('div');
const root = ReactDOMClient.createRoot(app);
root.render(<App relationships={relationships} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>dog</b>:5</td>
</tr>
<tr>
<td>wolf:10</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
Store components
This is the collection of store components within the ui-react-dom module. There are 6 store components in total.
TableInHtmlTable
The TableInHtmlTable component renders the contents of a single Table in a Store as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
TableInHtmlTable(props: TableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | TableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <TableInHtmlTable /> demo for this component in action.
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference).
This component renders a Table by iterating over its Row objects. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Store is provided. The TableInHtmlTable component within it then renders the Table in a <table> element with a CSS class.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <TableInHtmlTable tableId="pets" className="table" />;
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>species</th>
</tr>
</thead>
<tbody>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
<tr>
<th>felix</th>
<td>cat</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store is provided. The TableInHtmlTable component within it then renders the Table with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<TableInHtmlTable
tableId="pets"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId, cellId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td><b>fido</b>:dog</td>
</tr>
<tr>
<td>felix:cat</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
SortedTableInHtmlTable
The SortedTableInHtmlTable component renders the contents of a single sorted Table in a Store, as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
SortedTableInHtmlTable(props: SortedTableInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SortedTableInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <SortedTableInHtmlTable /> demo for this component in action.
The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference). It also takes a Cell Id to sort by and a boolean to indicate that the sorting should be in descending order. The offset and limit props are used to paginate results, but default to 0 and undefined to return all available Row Ids if not specified.
This component renders a ResultTable by iterating over its Row objects, in the order dictated by the sort parameters. By default the Cells are in turn rendered with the CellView component, but you can override this behavior by providing a component for each Cell in the customCells prop. You can pass additional props to that custom component with the getComponentProps callback. See the CustomCell type for more details.
This component uses the useSortedRowIds hook under the covers, which means that any changes to the structure or sorting of the Table will cause a re-render.
You can use the headerRow and idColumn props to control whether the Ids appear in a <th> element at the top of the table, and the start of each row.
The sortOnClick prop makes the table's sorting interactive such that the user can click on a column heading to sort by that column. The style classes sorted and ascending (or descending) are added so that you can provide hints to the user how the sorting is being applied.
Provide a paginator component for the Table with the paginator prop. Set to true to use the default SortedTablePaginator, or provide your own component that accepts SortedTablePaginatorProps.
Finally, the onChange prop lets you listen to a user's changes to the Table's sorting or pagination.
Examples
This example creates a Provider context into which a default Store is provided. The SortedTableInHtmlTable component within it then renders the Table in a <table> element with a CSS class.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
className="table"
/>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="table">
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ species</th>
</tr>
</thead>
<tbody>
<tr>
<th>felix</th>
<td>cat</td>
</tr>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store is provided. The SortedTableInHtmlTable component within it then renders the Table with a custom component and a custom props callback for the species Cell. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
customCells={customCells}
headerRow={false}
idColumn={false}
/>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
<>
{bold ? <b>{rowId}</b> : rowId}:
<CellView tableId={tableId} rowId={rowId} cellId={cellId} />
</>
);
const customCells = {
species: {
component: FormattedCellView,
getComponentProps: (rowId, cellId) => ({bold: rowId == 'fido'}),
},
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr>
<td>felix:cat</td>
</tr>
<tr>
<td><b>fido</b>:dog</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
SortedTablePaginator
The SortedTablePaginator component renders a paginator for a sorted table.
SortedTablePaginator(props: SortedTablePaginatorProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | SortedTablePaginatorProps | The props for this component. |
| returns | ComponentReturnType | The rendering of a paginator control with a label, and next and previous buttons, where appropriate. |
See the <SortedTableInHtmlTable /> demo for this component in action.
The component displays 'previous' and 'next' buttons for paging through the Table if there are more Row Ids than fit in each page. The component will also display a label that shows which Row Ids are being displayed.
The component's props identify initial pagination settings, and it will fire an event when the pagination changes.
Example
This example creates a Provider context into which a default Store is provided. The SortedTableInHtmlTable component within it then renders the Table in a <table> element with a SortedTablePaginator (the default).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<SortedTableInHtmlTable
tableId="pets"
cellId="species"
limit={2}
paginator={true}
/>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
lowly: {species: 'worm'},
polly: {species: 'parrot'},
},
});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<caption>
<button class="previous" disabled="">←</button>
<button class="next">→</button>
1 to 2 of 5 rows
</caption>
<thead>
<tr>
<th>Id</th>
<th class="sorted ascending">↑ species</th>
</tr>
</thead>
<tbody>
<tr>
<th>felix</th>
<td>cat</td>
</tr>
<tr>
<th>fido</th>
<td>dog</td>
</tr>
</tbody>
</table>
`;
Since
v4.1.0
ValuesInHtmlTable
The ValuesInHtmlTable component renders the keyed value contents of a Store as an HTML <table> element, and registers a listener so that any changes to that result will cause a re-render.
ValuesInHtmlTable(props: ValuesInHtmlTableProps & HtmlTableProps): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValuesInHtmlTableProps & HtmlTableProps | The props for this component. |
| returns | ComponentReturnType | A rendering of the |
See the <ValuesInHtmlTable /> demo for this component in action.
The component's props identify which Row to render based on Table Id, Row Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
This component renders a Store by iterating over its Value objects. By default the Values are in turn rendered with the ValueView component, but you can override this behavior by providing a valueComponent prop, a custom component of your own that will render a Value based on ValueProps. You can also pass additional props to your custom component with the getValueComponentProps callback prop.
This component uses the useValueIds hook under the covers, which means that any changes to the structure of the Values in the Store will cause a re-render.
You can use the headerRow and idColumn props to control whether labels and Ids appear in a <th> element at the top of the table, and the start of each row.
Examples
This example creates a Provider context into which a default Store is provided. The ValuesInHtmlTable component within it then renders the Values in a <table> element with a CSS class.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <ValuesInHtmlTable className="values" />;
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table class="values">
<thead>
<tr>
<th>Id</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>open</th>
<td>true</td>
</tr>
<tr>
<th>employees</th>
<td>3</td>
</tr>
</tbody>
</table>
`;
This example creates a Provider context into which a default Store is provided. The ValuesInHtmlTable component within it then renders the Row with a custom Cell component and a custom props callback. The header row at the top of the table and the Id column at the start of each row is removed.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<ValuesInHtmlTable
valueComponent={FormattedValueView}
getValueComponentProps={(valueId) => ({bold: valueId == 'open'})}
headerRow={false}
idColumn={false}
/>
);
const FormattedValueView = ({valueId, bold}) => (
<>
{bold ? <b>{valueId}</b> : valueId}
{': '}
<ValueView valueId={valueId} />
</>
);
const store = createStore().setValues({open: true, employees: 3});
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<table>
<tbody>
<tr><td><b>open</b>: true</td></tr>
<tr><td>employees: 3</td></tr>
</tbody>
</table>
`;
Since
v4.1.0
EditableCellView
The EditableCellView component renders the value of a single Cell in a way that can be edited in a web browser, and registers a listener so that any changes to that result will cause a re-render.
EditableCellView(props: CellProps & {className?: string}): ComponentReturnType| Type | Description | |
|---|---|---|
props | CellProps & {className?: string} | The props for this component. |
| returns | ComponentReturnType | An editable rendering of the |
See the <EditableCellView /> demo for this component in action.
The component's props identify which Cell to render based on Table Id, Row Id, Cell Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Cell contains a string, number, or boolean, so the value is rendered in an appropriate <input> tag and a button lets the user change type, if possible.
This component uses the useCell hook under the covers, which means that any changes to the specified Cell outside of this component will cause a re-render.
You can provide a custom className prop which well be used on the root of the resulting element. If omitted the element's class will be editableCell. The debugIds prop has no effect on this component.
Example
This example creates a Provider context into which a default Store is provided. The EditableCellView component within it then renders an editable Cell.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<EditableCellView tableId="pets" rowId="fido" cellId="color" />
);
const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<div class="editableCell">
<button class="string">string</button>
<input value="brown">
</div>
`;
Since
v4.1.0
EditableValueView
The EditableValueView component renders the value of a single Value in a way that can be edited in a web browser, and registers a listener so that any changes to that result will cause a re-render.
EditableValueView(props: ValueProps & {className?: string}): ComponentReturnType| Type | Description | |
|---|---|---|
props | ValueProps & {className?: string} | The props for this component. |
| returns | ComponentReturnType | An editable rendering of the |
See the <EditableValueView /> demo for this component in action.
The component's props identify which Value to render based on Table Id, Row Id, Value Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).
A Value contains a string, number, or boolean, so the value is rendered in an appropriate <input> tag and a button lets the user change type, if possible.
This component uses the useValue hook under the covers, which means that any changes to the specified Value outside of this component will cause a re-render.
You can provide a custom className prop which well be used on the root of the resulting element. If omitted the element's class will be editableValue. The debugIds prop has no effect on this component.
Example
This example creates a Provider context into which a default Store is provided. The EditableValueView component within it then renders an editable Value.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <EditableValueView valueId="employees" />;
const store = createStore().setValue('employees', 3);
const app = document.createElement('div');
ReactDOMClient.createRoot(app).render(<App store={store} />);
console.log(app.innerHTML);
// ->
`
<div class="editableValue">
<button class="number">number</button>
<input type="number" value="3">
</div>
`;
Since
v4.1.0
Type Aliases
These are the type aliases within the ui-react-dom module.
Configuration type aliases
This is the collection of configuration type aliases within the ui-react-dom module. There are only two configuration type aliases, CustomCell and CustomResultCell.
CustomCell
The CustomCell object is used to configure custom cell rendering in an HTML table.
{
label?: string;
component?: ComponentType<CellProps>;
getComponentProps?: (rowId: string, cellId: string): ExtraProps;
}| Type | Description | |
|---|---|---|
label? | string | An optional string that will be used as the label at the top of the table column for this |
component? | ComponentType<CellProps> | An optional custom component for rendering each |
getComponentProps? | getComponentProps(rowId: string, cellId: string): ExtraProps | An optional function for generating extra props for each custom |
Since
v4.1.0
CustomResultCell
The CustomResultCell object is used to configure custom cell rendering for query results in an HTML table.
{
label?: string;
component?: ComponentType<ResultCellProps>;
getComponentProps?: (rowId: string, cellId: string): ExtraProps;
}| Type | Description | |
|---|---|---|
label? | string | An optional string that will be used as the label at the top of the table column for this |
component? | ComponentType<ResultCellProps> | An optional custom component for rendering each |
getComponentProps? | getComponentProps(rowId: string, cellId: string): ExtraProps | An optional function for generating extra props for each custom |
Since
v4.1.0
Props type aliases
This is the collection of props type aliases within the ui-react-dom module. There are 10 props type aliases in total.
StoreInspectorProps
StoreInspectorProps props are used to configure the StoreInspector component.
{
position?: "left" | "top" | "bottom" | "right" | "full";
open?: boolean;
}| Type | Description | |
|---|---|---|
position? | "left" | "top" | "bottom" | "right" | "full" | An optional string to indicate where you want the inspector to first appear. |
open? | boolean | An optional boolean to indicate whether the inspector should start in the opened state. |
Since
v4.1.0
TableInHtmlTableProps
TableInHtmlTableProps props are used for components that will render a Table in an HTML table, such as the TableInHtmlTable component.
{
tableId: Id;
store?: StoreOrStoreId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
}| Type | Description | |
|---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
Since
v4.1.0
HtmlTableProps
HtmlTableProps props are used for components that will render in an HTML table, such as the TableInHtmlTable component or SortedTableInHtmlTable component.
{
className?: string;
headerRow?: boolean;
idColumn?: boolean;
}| Type | Description | |
|---|---|---|
className? | string | A string className to use on the root of the resulting element. |
headerRow? | boolean | Whether a header row should be rendered at the top of the table, defaulting to |
idColumn? | boolean | Whether an |
Since
v4.1.0
RelationshipInHtmlTableProps
RelationshipInHtmlTableProps props are used for components that will render the contents of the two Tables linked by a Relationship as an HTML table, such as the RelationshipInHtmlTable component.
{
relationshipId: Id;
relationships?: RelationshipsOrRelationshipsId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
}| Type | Description | |
|---|---|---|
relationshipId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of dotted 'tableId.cellId' string pairs to use for rendering a prescribed set of the relationship |
Note the use of dotted 'tableId.cellId' string pairs when specifying custom rendering for the cells in this table, since Cells from both the relationship's 'local' and 'remote' Table objects can be rendered and need to be distinguished
Since
v4.1.0
ResultSortedTableInHtmlTableProps
ResultSortedTableInHtmlTableProps props are used for components that will render a sorted Table in an HTML table, such as the SortedTableInHtmlTable component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
customCells?: Ids | {[cellId: Id]: string | CustomResultCell};
sortOnClick?: boolean;
paginator?: boolean | ComponentType<SortedTablePaginatorProps>;
onChange?: (sortAndOffset: [cellId: string, descending: boolean, offset: number]): void;
}| Type | Description | |
|---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
customCells? | Ids | {[cellId: Id]: string | CustomResultCell} | An optional list of |
sortOnClick? | boolean | Whether the table should be interactive such that clicking a header changes the sorting and/or direction. |
paginator? | boolean | ComponentType<SortedTablePaginatorProps> | Either |
onChange? | onChange(sortAndOffset: [cellId: string, descending: boolean, offset: number]): void | A function that is called whenever the sorting or pagination of the |
Since
v4.1.0
ResultTableInHtmlTableProps
ResultTableInHtmlTableProps props are used for components that will render a ResultTable in an HTML table, such as the ResultTableInHtmlTable component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
customCells?: Ids | {[cellId: Id]: string | CustomResultCell};
}| Type | Description | |
|---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
customCells? | Ids | {[cellId: Id]: string | CustomResultCell} | An optional list of |
Since
v4.1.0
SliceInHtmlTableProps
SliceInHtmlTableProps props are used for components that will render an Index Slice in an HTML table, such as the SliceInHtmlTable component.
{
indexId: Id;
sliceId: Id;
indexes?: IndexesOrIndexesId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
}| Type | Description | |
|---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
Since
v4.1.0
SortedTableInHtmlTableProps
SortedTableInHtmlTableProps props are used for components that will render a sorted Table in an HTML table, such as the SortedTableInHtmlTable component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
editable?: boolean;
customCells?: Ids | {[cellId: Id]: string | CustomCell};
sortOnClick?: boolean;
paginator?: boolean | ComponentType<SortedTablePaginatorProps>;
onChange?: (sortAndOffset: [cellId: string, descending: boolean, offset: number]): void;
}| Type | Description | |
|---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
store? | StoreOrStoreId | The |
editable? | boolean | Whether the Cells should be editable. This affects the default |
customCells? | Ids | {[cellId: Id]: string | CustomCell} | An optional list of |
sortOnClick? | boolean | Whether the table should be interactive such that clicking a header changes the sorting and/or direction. |
paginator? | boolean | ComponentType<SortedTablePaginatorProps> | Either |
onChange? | onChange(sortAndOffset: [cellId: string, descending: boolean, offset: number]): void | A function that is called whenever the sorting or pagination of the |
Since
v4.1.0
SortedTablePaginatorProps
SortedTablePaginatorProps props are used for components that will be used as a table paginator, such as the SortedTablePaginator component.
{
onChange: (offset: number): void;
offset?: number;
limit?: number;
total: number;
singular?: string;
plural?: string;
}| Type | Description | |
|---|---|---|
onChange | onChange(offset: number): void | An event that will fire when the offset is updated, called with the new offset. |
offset? | number | |
limit? | number | |
total | number | |
singular? | string | A noun to use in the pagination label for a single row, defaulting to 'row'. |
plural? | string | A noun to use in the pagination label for multiple rows, defaulting to the value of the singular noun suffixed with the letter 's'. |
Since
v4.1.0
ValuesInHtmlTableProps
ValuesInHtmlTableProps props are used for components that will render Values in an HTML table, such as the ValuesInHtmlTable component.
{
store?: StoreOrStoreId;
editable?: boolean;
valueComponent?: ComponentType<ValueProps>;
getValueComponentProps?: (valueId: string): ExtraProps;
}| Type | Description | |
|---|---|---|
store? | StoreOrStoreId | The |
editable? | boolean | Whether the |
valueComponent? | ComponentType<ValueProps> | A custom component for rendering each |
getValueComponentProps? | getValueComponentProps(valueId: string): ExtraProps | A function for generating extra props for each custom |
Since
v4.1.0